package com.cmc.cloud.cmclink.doc.service.impl.si;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.shaded.com.google.protobuf.DescriptorProtos;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cmc.cloud.cmclink.bdt.api.base.PortApi;
import com.cmc.cloud.cmclink.bdt.api.base.VesselDataApi;
import com.cmc.cloud.cmclink.bdt.api.base.dto.req.PortListByCodeReqDTO;
import com.cmc.cloud.cmclink.bdt.api.base.dto.req.VesselNameListByImoCodesReqDTO;
import com.cmc.cloud.cmclink.bdt.api.base.dto.resp.PortListByCodeRespDTO;
import com.cmc.cloud.cmclink.bdt.api.base.dto.resp.VesselNamesRespDTO;
import com.cmc.cloud.cmclink.doc.api.document.emums.*;
import com.cmc.cloud.cmclink.doc.constants.SiPaymentConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.BlnoBlPrintLogErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.BlnoFreightErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.convert.BlnoBlPrintLogConvert;
import com.cmc.cloud.cmclink.doc.entity.*;
import com.cmc.cloud.cmclink.doc.enums.PrintBlnoUrlEnum;
import com.cmc.cloud.cmclink.doc.enums.PrintStatusEnum;
import com.cmc.cloud.cmclink.doc.enums.ResponseContentTypeEnum;
import com.cmc.cloud.cmclink.doc.mapper.*;
import com.cmc.cloud.cmclink.doc.rpcservice.LaneService;
import com.cmc.cloud.cmclink.doc.rpcservice.VesselTransferService;
import com.cmc.cloud.cmclink.doc.rpcservice.VoyScheduleService;
import com.cmc.cloud.cmclink.doc.rpcservice.constants.RpcErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.rpcservice.impl.CustomerRpcServiceImpl;
import com.cmc.cloud.cmclink.doc.rpcservice.vo.LaneRespVO;
import com.cmc.cloud.cmclink.doc.service.FineReportUrlService;
import com.cmc.cloud.cmclink.doc.service.PortDescService;
import com.cmc.cloud.cmclink.doc.service.si.BlnoBlPrintLogService;
import com.cmc.cloud.cmclink.doc.strategy.DocumentObjectivesStrategyFactory;
import com.cmc.cloud.cmclink.doc.util.ByteToMultipartFile;
import com.cmc.cloud.cmclink.doc.util.FileOperateUtil;
import com.cmc.cloud.cmclink.doc.util.PdfMerger;
import com.cmc.cloud.cmclink.doc.util.ZipCompressor;
import com.cmc.cloud.cmclink.doc.vo.blnoblprintlogvo.*;
import com.cmc.cloud.cmclink.doc.vo.finereporturlvo.FineReportUrlReqVO;
import com.cmc.cloud.cmclink.doc.vo.finereporturlvo.FineReportUrlRespVO;
import com.cmc.cloud.cmclink.svl.api.base.enums.SystemEnums;
import com.cmc.cloud.cmclink.svl.api.base.req.VesselVoyReqDTO;
import com.cmc.cloud.cmclink.svl.api.base.resp.VesselVoyRespDTO;
import com.cmc.cloud.cmclink.system.api.api.dict.DictDataApi;
import com.cmc.cloud.cmclink.system.api.api.dict.dto.DictDataRespDTO;
import com.cmc.cloud.cmclink.system.api.api.file.FileApi;
import com.cmc.cloud.cmclink.system.api.api.file.dto.FileUploadReqDTO;
import com.cmc.cloud.cmclink.system.api.api.file.dto.FileUploadRespDTO;
import com.cmes.framework.common.exception.util.ServiceExceptionUtil;
import com.cmes.framework.common.pojo.CommonResult;
import com.cmes.framework.common.pojo.PageResult;
import com.cmes.framework.mybatis.core.query.LambdaQueryWrapperX;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.DecimalFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

import static com.cmc.cloud.cmclink.doc.util.ZipCompressor.ZIP;


/**
 * 提单打印 Service 实现类
 *
 * @author 集运管理员
 */
@Service
@Validated
public class BlnoBlPrintLogServiceImpl implements BlnoBlPrintLogService {

    private static final Logger log = LoggerFactory.getLogger(BlnoBlPrintLogServiceImpl.class);
    private static final DateTimeFormatter FORMATTER_AMEND = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
    public static final String FILE_BUSINESS_POINT = "提单打印";
    public static final String PDF = ".pdf";
    public static final String CONTENT_TYPE = "application/pdf";
    public static final String FILE = "file";
    public static final String RF = "RF";
    public static final String RD = "RD";
    public static final String TEMP = "TEMP:";
    public static final String VENT = "VENT:";
    public static final String DG = "DG";
    public static final String UNNO = "UNNO:";
    public static final String CLASS = "CLASS:";
    public static final String MARK_SEE_ATTACHME = "MARKS SEE ATTACHME";
    public static final String CONTAINERS_SEE_ATTACHMENT = "CONTAINERS SEE ATTACHMENT";
    public static final String GOODS_DES_SEE_ATTACHMENT = "DESCRIPTION OF GOODS"+System.lineSeparator()+System.lineSeparator()+"SEE ATTACHMENT";
    private static final String TEL = "TEL:";
    private static final String EMAIL = "EMAIL:";
    private static final String FAX = "FAX:";
    private static final String SHIPPER_CONNECTOR = "*";
    private static final String CONSIGNEE_CONNECTOR = "**";
    private static final String NOTIFY_CONNECTOR = "***";
    private static final String PRINT_TYPE_COPYE = "COPY";
    private static final String FIRST_PAGE = "FIRST_PAGE";
    private static final String SECOND_PAGE = "SECOND_PAGE";
    private static final String PRINT_OPERATION = "Y";
    public static final String TMP = "tmp";
    private static final String CNSHA = "CNSHA";
    private static final int MARKS_CONTAINERS_MAX_COLUMN = 31;
    private static final int MARKS_CONTAINERS_MAX_LINE = 26;
    private static final int SCN_MAX_COLUMN = 57;
    private static final int SCN_MAX_LINE = 7;
    private static final int GOODS_DESC_SCN_MAX_COLUMN = 34;
    private static final int GOODS_DESC_SCN_MAX_LINE_17 = 17;
    private static final int GOODS_DESC_SCN_MAX_LINE_23 = 23;




    @Value("${pdf.tag}")
    private String tag;
    @Value("${pdf.url}")
    private String url;
    @Resource
    private BlnoBlPrintLogMapper blnoBlPrintLogMapper;

    @Resource
    private BlnoVslvoyMapper blnoVslvoyMapper;

    @Resource
    private BlnoScnMapper blnoScnMapper;

    @Resource
    private BlnoCargoMapper blnoCargoMapper;

    @Resource
    private BlnoCntrMapper blnoCntrMapper;

    @Resource
    private BlnoPaymentMapper blnoPaymentMapper;

    @Resource
    private FileApi fileApi;

    @Resource
    private DictDataApi dictDataApi;

    @Resource
    private BlnoMapper blnoMapper;

    @Resource
    private DocumentObjectivesStrategyFactory documentObjectivesStrategyFactory;

    @Resource
    private VesselTransferService vesselTransferService;

    @Resource
    private PortDescMapper portDescMapper;

    @Resource
    private BlnoContactMapper blnoContactMapper;

    @Resource
    private BaseCodeMapper baseCodeMapper;

    @Resource
    private VesselDataApi vesselDataApi;

    @Resource
    private PortApi portApi;

    @Autowired
    private CustomerRpcServiceImpl nameTransferService;

    @Resource
    private VoyScheduleService voyScheduleService;

    @Resource
    private FineReportUrlService fineReportUrlService;

    @Resource
    private PortDescService portDescService;

    @Resource
    private LaneService laneService;

    @Override
    public void createBlnoBlPrintLog(BlnoBlPrintLogCreateReqVO createReqVO) {
        // 插入
        BlnoBlPrintLogDO blnoBlPrintLog = BlnoBlPrintLogConvert.INSTANCE.convert(createReqVO);
        blnoBlPrintLogMapper.insert(blnoBlPrintLog);
    }

    @Override
    public List<BlnoBlPrintLogDO> billsHistoricalRecords(BlnoBlPrintLogListByBlnoIdReqVO reqVO) {
        if (reqVO == null || reqVO.getBlnoId() == null) {
            throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_ID_NOT_NULL);
        }
        LambdaQueryWrapperX<BlnoBlPrintLogDO> queryWrapper = new LambdaQueryWrapperX<>();
        queryWrapper.eq(BlnoBlPrintLogDO::getBlnoId, reqVO.getBlnoId())
                .eq(BlnoBlPrintLogDO::getDeleted, false)
                .orderByDesc(BlnoBlPrintLogDO::getCreateTime);
        return blnoBlPrintLogMapper.selectList(queryWrapper);
    }

    /**
     * 获取第一个符合条件的航线代码
     *
     * @param vslvoyList 提单航线船期列表
     * @return 航线代码
     */
    private BlnoVslvoyDO getFirstLaneCode(List<BlnoVslvoyDO> vslvoyList) {
        if (CollectionUtils.isEmpty(vslvoyList)) {
            return null;
        }
        // 筛选运输模式为LINER的记录，并按航程序号排序取第一个
        Optional<BlnoVslvoyDO> blnoOptinal = vslvoyList.stream()
                .filter(vslvoy -> TransmodeEnum.LINER.getValue().equals(vslvoy.getTransmode()))
                .min(Comparator.comparingInt(BlnoVslvoyDO::getRouteNo));
        BlnoVslvoyDO blnoVslvoyDO = blnoOptinal.orElse(null);
        return blnoVslvoyDO;
    }


    @Override
    public void updateBlnoBlPrintLog(BlnoBlPrintLogUpdateReqVO updateReqVO) {
        // 校验存在
        validateBlnoBlPrintLogExists(updateReqVO.getId());
        // 更新
        BlnoBlPrintLogDO updateObj = BlnoBlPrintLogConvert.INSTANCE.convert(updateReqVO);
        blnoBlPrintLogMapper.updateById(updateObj);
    }

    @Override
    public void deleteBlnoBlPrintLog(Long id) {
        // 校验存在
        validateBlnoBlPrintLogExists(id);
        // 删除
        blnoBlPrintLogMapper.deleteById(id);
    }

    private void validateBlnoBlPrintLogExists(Long id) {
        if (blnoBlPrintLogMapper.selectById(id) == null) {
            throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_BL_PRINT_LOG_NOT_EXISTS);
        }
    }

    @Override
    public BlnoBlPrintLogDO getBlnoBlPrintLog(Long id) {
        return blnoBlPrintLogMapper.selectById(id);
    }

    @Override
    public List<BlnoBlPrintLogDO> getBlnoBlPrintLogList(Collection<Long> ids) {
        return blnoBlPrintLogMapper.selectBatchIds(ids);
    }

    @Override
    public PageResult<BlnoBlPrintLogDO> getBlnoBlPrintLogPage(BlnoBlPrintLogPageReqVO reqVO) {
        IPage<BlnoBlPrintLogDO> page = Page.of(reqVO.getPageNo(), reqVO.getPageSize());

        IPage<BlnoBlPrintLogDO> result = blnoBlPrintLogMapper.selectPageList(page, reqVO);
        return new PageResult<>(result.getRecords(), result.getTotal());
    }

    @Override
    public PageResult<PrintBlRespVo> getPrintBlPage(PrintBlReqVo pageReqVO) {
        IPage<PrintBlRespVo> page = Page.of(pageReqVO.getPageNo(), pageReqVO.getPageSize());
        IPage<PrintBlRespVo> result = blnoBlPrintLogMapper.getPrintPage(page, pageReqVO);
        // 设置打印状态
        List<PrintBlRespVo> records = result.getRecords();
        for (PrintBlRespVo record : records) {
            if (record.getIssueFieId() != null || record.getDraftFileId() != null) {
                record.setPrintStatus("Y");
            } else {
                record.setPrintStatus("N");
            }
            record.setSiStatus(SiStatusEnum.getLabelByValue(record.getSiStatus()));
        }
        //订舱人编码转英文名
        if (!CollectionUtils.isEmpty(records)){
            List<String> collect = records.stream().map(PrintBlRespVo::getBookingParty).collect(Collectors.toList());
            Map<String, String> companyNameMap = nameTransferService.getCompanyNameMap(collect);
            records.stream().forEach(t -> {
                t.setBookingPartyEn(companyNameMap.get(t.getBookingParty()));
            });
        }
        return new PageResult<>(records, result.getTotal());
    }


    /**
     * 批量打印
     *
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public File batchPrintBl(BlnoPrintBatchOperationReqDto blnoPrintBatchOperationReqDto) {
        List<BlnoDO> blnoDos = blnoMapper.selectList(new LambdaQueryWrapperX<BlnoDO>()
                .in(BlnoDO::getId, blnoPrintBatchOperationReqDto.getBlIds())
                .eq(BlnoDO::getValid, SiValidEnum.VALID.getValue())
                .in(BlnoDO::getSiStatus, Arrays.asList(
                        SiStatusEnum.BookingPendingSpace.getValue(),
                        SiStatusEnum.AwaitingMaterialUploaded.getValue(),
                        SiStatusEnum.BookingConfirmed.getValue(),
                        SiStatusEnum.BookingRejected.getValue()
                ))
                .eq(BlnoDO::getPodBlFlag, true));
        List<File> files = new ArrayList<>();
        List<File> attFiles = new ArrayList<>();
        blnoDos.forEach(blno -> {
            // 根据提单ID查询数据
            BlnoBlPrintLogDO blnoBlPrintLogDO = Optional.ofNullable(blnoBlPrintLogMapper.selectOne(
                    new LambdaQueryWrapper<BlnoBlPrintLogDO>().eq(BlnoBlPrintLogDO::getBlnoId, blno.getId())
            )).orElse(new BlnoBlPrintLogDO());
            boolean isExistFile = false;
            //已存在文件直接用
            if (blnoBlPrintLogDO.getId() != null) {
                if ((PrintStatusEnum.DRAFTBL.getValue().equalsIgnoreCase(blnoPrintBatchOperationReqDto.getType()) || PrintStatusEnum.PREVIEWBL.getValue().equalsIgnoreCase(blnoPrintBatchOperationReqDto.getType()))){
                    File printedFile = this.getPrintedFile(blnoBlPrintLogDO, blnoPrintBatchOperationReqDto.getType());
                    if (!ObjectUtils.isEmpty(printedFile) ){
                        files.add(printedFile);
                        isExistFile = true;
                    };
                }else if (PrintStatusEnum.PRINTBL.getValue().equalsIgnoreCase(blnoPrintBatchOperationReqDto.getType())){
                    isExistFile = this.getPrintBlandFlag(blnoBlPrintLogDO,blnoPrintBatchOperationReqDto,files,attFiles);
                }else {
                    //打印操作类型不正确 todo
                    throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_STATUS_ILLEGAL);
                }
            }

            if (!isExistFile){
                Map<String, File> stringFileMap = printBlNo(blno, blnoPrintBatchOperationReqDto.getType());
                //多个文件时说明有附件页，一个文件时直接返回 todo 本地临时文件待删除
                if (ObjectUtils.isEmpty(stringFileMap)){
                    throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.FILE_NOT_GENERATED);
                }else if (stringFileMap.size() > 1){
                    files.add(stringFileMap.get(FIRST_PAGE));
                    attFiles.add(stringFileMap.get(SECOND_PAGE));
                }else {
                    files.add(stringFileMap.get(FIRST_PAGE));
                }
            }
        });
        //定义输出文件
        File respFile = this.getRespFile(files, blnoPrintBatchOperationReqDto);
        File respAttFile = this.getRespAttFile(attFiles);
        UUID zipUuid = UUID.randomUUID();
        boolean existRespFile = !ObjectUtils.isEmpty(respFile) && respFile.exists();
        boolean existRespAttFile =  !ObjectUtils.isEmpty(respAttFile) && respAttFile.exists();
        if (existRespFile && existRespAttFile){
            String zipFileName = blnoPrintBatchOperationReqDto.getType().toUpperCase() + "_" +zipUuid+ZipCompressor.ZIP;
            String zipPathName = FileOperateUtil.getResourcePath() + File.separator + zipFileName;

            ZipCompressor.compressMultipleFiles(Arrays.asList(respFile,respAttFile),zipPathName);
            File file = new File(zipPathName);
            if (file.exists() && file.length()>0){
                return file;
            }else {
                throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_FILE_ZIP_FAIL);
            }
        } else {
            if (!ObjectUtils.isEmpty(respFile) && respFile.exists()){
                return respFile;
            }
            if (!ObjectUtils.isEmpty(respAttFile) && respAttFile.exists()){
                return respAttFile;
            }
        }
        return null;
    }

    private boolean getPrintBlandFlag(BlnoBlPrintLogDO blnoBlPrintLogDO,BlnoPrintBatchOperationReqDto blnoPrintBatchOperationReqDto,List<File> files ,List<File> attFiles){
        File printedAttFile = this.getPrintedFile(blnoBlPrintLogDO, PrintStatusEnum.PRINTATT.getValue());
        if (ObjectUtils.isEmpty(printedAttFile)){
            //没有附件则直接将纸质首页返回
            File printedFile = this.getPrintedFile(blnoBlPrintLogDO, blnoPrintBatchOperationReqDto.getType());
            if (!ObjectUtils.isEmpty(printedFile) ){
                files.add(printedFile);
                return true;
            };
        }else {
            //选择PRINTBL打印时且有附件
            File printedBlFile = this.getPrintedFile(blnoBlPrintLogDO, blnoPrintBatchOperationReqDto.getType());
            if (!ObjectUtils.isEmpty(printedBlFile)){
                files.add(printedBlFile);
                attFiles.add(printedAttFile);
                return true;
            }
        }
        return false;
    }

    private File getRespFile(List<File> files,BlnoPrintBatchOperationReqDto blnoPrintBatchOperationReqDto){
        File respFile = null;
        UUID uuid = UUID.randomUUID();
        if (!ObjectUtils.isEmpty(files) && files.size() >1){
            String tempDir = FileOperateUtil.getResourcePath()+File.separator + TMP+ uuid;
            String targetPdf = FileOperateUtil.getResourcePath()+File.separator + blnoPrintBatchOperationReqDto.getType().toUpperCase() + "_" + uuid+PDF;
            try {
                PdfMerger.safeMergePdfs(files,targetPdf,tempDir);
                respFile = new File(targetPdf);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }else if (!ObjectUtils.isEmpty(files) && files.size() == 1){
            respFile = files.get(0);
        }
        return respFile;
    }

    private File getRespAttFile(List<File> attFiles){
        File respAttFile = null;
        UUID attUuid = UUID.randomUUID();
        if (!ObjectUtils.isEmpty(attFiles) && attFiles.size() >1){
            String tempDir = FileOperateUtil.getResourcePath()+File.separator + TMP+ attUuid;
            String targetPdf = FileOperateUtil.getResourcePath()+File.separator + PrintStatusEnum.PRINTATT.getValue().toUpperCase() + "_" + attUuid + PDF;
            try {
                PdfMerger.safeMergePdfs(attFiles,targetPdf,tempDir);
                respAttFile = new File(targetPdf);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }else if (!ObjectUtils.isEmpty(attFiles) && attFiles.size() == 1){
            respAttFile = attFiles.get(0);
        }
        return respAttFile;
    }

    /**
     * 逐单打印
     *
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public byte[] pdfCreateAnEdit(BlnoBlPrintLogCreateReqVO createReqVO) {
        if (createReqVO.getBlnoId() == null) {
            throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_ID_NOT_NULL);
        }
        BlnoDO blnoDO = blnoMapper.selectOne(new LambdaQueryWrapper<BlnoDO>()
                .eq(BlnoDO::getId, createReqVO.getBlnoId())
                .eq(BlnoDO::getValid, SiValidEnum.VALID.getValue())
                .in(BlnoDO::getSiStatus, Arrays.asList(
                        SiStatusEnum.BookingPendingSpace.getValue(),
                        SiStatusEnum.AwaitingMaterialUploaded.getValue(),
                        SiStatusEnum.BookingConfirmed.getValue(),
                        SiStatusEnum.BookingRejected.getValue()
                ))
                .eq(BlnoDO::getPodBlFlag, true));
      if(ObjectUtils.isEmpty(blnoDO)){
          throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_NOT_EXISTS);
      }

        // 根据提单ID查询数据
        BlnoBlPrintLogDO blnoBlPrintLogDO = Optional.ofNullable(blnoBlPrintLogMapper.selectOne(
                new LambdaQueryWrapper<BlnoBlPrintLogDO>().eq(BlnoBlPrintLogDO::getBlnoId, createReqVO.getBlnoId())
        )).orElse(new BlnoBlPrintLogDO());


        //已存在文件直接用
        if (blnoBlPrintLogDO.getId() != null) {
            if ((PrintStatusEnum.DRAFTBL.getValue().equalsIgnoreCase(createReqVO.getType()) || PrintStatusEnum.PREVIEWBL.getValue().equalsIgnoreCase(createReqVO.getType()))){
                byte[] printedFile = this.getPrintedFileBytes(blnoBlPrintLogDO, createReqVO.getType());
                if (!ObjectUtils.isEmpty(printedFile) ){
                    createReqVO.setFileName(this.getPrintedBlFileName(blnoBlPrintLogDO,createReqVO.getType()));
                    return printedFile;
                };
            }else if (PrintStatusEnum.PRINTBL.getValue().equalsIgnoreCase(createReqVO.getType())){
                byte[] printedAttFile = this.getPrintedFileBytes(blnoBlPrintLogDO, PrintStatusEnum.PRINTATT.getValue());
                if (ObjectUtils.isEmpty(printedAttFile)){
                    //没有附件则直接将纸质首页返回
                    byte[] printedFile = this.getPrintedFileBytes(blnoBlPrintLogDO, createReqVO.getType());
                    if (!ObjectUtils.isEmpty(printedFile) ){
                        createReqVO.setFileName(this.getPrintedBlFileName(blnoBlPrintLogDO,createReqVO.getType()));
                        return printedFile;
                    };
                }else {
                    //选择PRINTBL打印时需且有附件
                    byte[] printedBlFile = this.getPrintedFileBytes(blnoBlPrintLogDO, createReqVO.getType());
                    if (!ObjectUtils.isEmpty(printedBlFile)){
                        HashMap<String, byte[]> fileMap = new HashMap<>(16);
                        fileMap.put(blnoDO.getBlNo()+"_"+PrintStatusEnum.PRINTBL+PDF,printedBlFile);
                        fileMap.put(blnoDO.getBlNo()+"_"+PrintStatusEnum.PRINTATT+PDF,printedAttFile);
                        String zipFileName = blnoDO.getBlNo()+"_"+PrintStatusEnum.PRINTBL+ZipCompressor.ZIP;
                        String zipFilePath = FileOperateUtil.getResourcePath() + File.separator + zipFileName;
                        File zipFile = new File(zipFilePath);
                        ZipCompressor.compressMultipleFiles(fileMap,zipFilePath);
                        if (zipFile.exists() && zipFile.length()>0){
                            createReqVO.setFileName(zipFileName);
                            return FileOperateUtil.readFileToByteArrayWithFileInputStream(zipFile);
                        }else {
                            throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_FILE_ZIP_FAIL);
                        }
                    }
                }
            }else {
                //打印操作类型不正确
                throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_NOT_PRINT_TYPE);
            }

        }

        Map<String, File> stringFileMap = printBlNo(blnoDO, createReqVO.getType());
        //多个文件时打包成压缩包返回，一个文件时直接返回 todo 本地临时文件待删除
        if (ObjectUtils.isEmpty(stringFileMap)){
            throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.FILE_NOT_GENERATED);
        }else if (stringFileMap.size() > 1){
            String zipFileName = blnoDO.getBlNo()+"_"+PrintStatusEnum.PRINTBL+ZipCompressor.ZIP;
            String pathName = FileOperateUtil.getResourcePath() + File.separator + zipFileName;
            File zipFile = new File(pathName);
            ZipCompressor.compressMultipleFiles(new ArrayList<>(stringFileMap.values()),pathName);
            if (zipFile.exists() && zipFile.length()>0){
                createReqVO.setFileName(zipFileName);
                return FileOperateUtil.readFileToByteArrayWithFileInputStream(zipFile);
            }else {
                throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_FILE_ZIP_FAIL);
            }
        }else {
            createReqVO.setFileName(stringFileMap.get(FIRST_PAGE).getName());
            return FileOperateUtil.readFileToByteArrayWithFileInputStream(stringFileMap.get(FIRST_PAGE));
        }
    }

    /**
     * 逐单打印处理
     * */
    private Map<String, File> printBlNo(BlnoDO blnoDO, String fileType){
        if(blnoDO == null){
            throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_STATUS_ILLEGAL);
        }

        // 校验打印条件
        this.checkPrintStatus(blnoDO,fileType);
        String blEntity = null;
        boolean issuePrint = PrintStatusEnum.PRINTBL.getValue().equalsIgnoreCase(fileType) || PrintStatusEnum.PREVIEWBL.getValue().equalsIgnoreCase(fileType);
        //print B/L(正本) ：提单主体（先看主表，再查LINE，如果LINE返回，回填主体字段），如果以上没查询到则打印阻断,提示提单主体为空。
        //preview B/L ：提单主体（先看主表，再查LINE，如果LINE返回，回填主体字段）为空，则打印阻断，提示提单主体为空。
        if (issuePrint){
            blEntity = this.checkBlEntity(blnoDO);
        }


        //预加载打印数据
        BlnoMaintenanceInfoRespVO blnoMaintenanceInfo = this.getBlnoMaintenanceInfo(blnoDO, null);
        boolean existAtt = !ObjectUtils.isEmpty(blnoMaintenanceInfo.getAttachment());
        //装船时间为空时只允许打印草稿，其他不允许打印
        if (issuePrint && ObjectUtils.isEmpty(blnoMaintenanceInfo.getLadenOnBoard())){
            throw ServiceExceptionUtil.exception(BlnoFreightErrorCodeConstants.BLNO_FREIGHT_ETU_IS_NULL);
        }
        //生成文件
        Map<String, File> stringFile = this.printBlNoFile(blnoDO, fileType,blEntity,existAtt);

        //上传并保存数据
        this.uploadFileAndSavePrintData(blnoDO,stringFile,fileType,blnoMaintenanceInfo);
        //更新提单数据
        this.updateBlno(blnoDO,fileType,blEntity);
        return stringFile;
    }

    /**
     * 提单主体刷新刷新Preview
     * */
    public Map<String, File> flushPrintFile(BlnoDO blnoDO, String fileType){
        if(blnoDO == null){
            throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_STATUS_ILLEGAL);
        }

        // 校验打印条件
        this.checkFlushPrint(blnoDO,fileType);
        String blEntity = null;
        boolean issuePrint = PrintStatusEnum.PRINTBL.getValue().equalsIgnoreCase(fileType) || PrintStatusEnum.PREVIEWBL.getValue().equalsIgnoreCase(fileType);
        //print B/L(正本) ：提单主体（先看主表，再查LINE，如果LINE返回，回填主体字段），如果以上没查询到则打印阻断,提示提单主体为空。
        //preview B/L ：提单主体（先看主表，再查LINE，如果LINE返回，回填主体字段）为空，则打印阻断，提示提单主体为空。
        if (issuePrint){
            blEntity = this.checkBlEntity(blnoDO);
        }


        //预加载打印数据
        BlnoMaintenanceInfoRespVO blnoMaintenanceInfo = this.getBlnoMaintenanceInfo(blnoDO, null);
        boolean existAtt = !ObjectUtils.isEmpty(blnoMaintenanceInfo.getAttachment());
        //装船时间为空时只允许打印草稿，其他不允许打印
        if (issuePrint && ObjectUtils.isEmpty(blnoMaintenanceInfo.getLadenOnBoard())){
            throw ServiceExceptionUtil.exception(BlnoFreightErrorCodeConstants.BLNO_FREIGHT_ETU_IS_NULL);
        }

        //生成文件
        Map<String, File> stringFile = this.printBlNoFile(blnoDO, fileType,blEntity,existAtt);

        //上传并保存数据
        this.uploadFileAndSavePrintData(blnoDO,stringFile,fileType,blnoMaintenanceInfo);
        return stringFile;
    }

    /**
     * 检查提单主体
     * @param blnoDO 提单主表对象
     * @return 返回航线提单主体，提单主表提单主体不为空时返回null，为空时查询航线提单主体并返回
     */
    public String checkBlEntity(BlnoDO blnoDO){
        String businessEntity = null;
        if (!ObjectUtils.isEmpty(blnoDO.getBlEntity())){
            businessEntity = blnoDO.getBlEntity();
        }else {
            //提单主体为空，从航线查询.入参以主表"bkg_vessel_code"，"bkg_voyage"匹配doc_blno_vslvoy的船名和出口航次，取"lane_code_ex"字段。
            LambdaQueryWrapper<BlnoVslvoyDO> vslvoyQuery = new LambdaQueryWrapper<>();
            vslvoyQuery.eq(BlnoVslvoyDO::getBlnoId, blnoDO.getId());
            List<BlnoVslvoyDO> vslvoyList = blnoVslvoyMapper.selectList(vslvoyQuery);
            BlnoVslvoyDO blnoVslvoyDo = vslvoyList.stream().filter(vs -> blnoDO.getBkgVesselCode().equals(vs.getVesselCode()) && blnoDO.getBkgVoyage().equals(vs.getVoyageEx())).findFirst().orElse(null);
            if (!ObjectUtils.isEmpty(blnoVslvoyDo)){
                List<LaneRespVO> laneRespVos = laneService.selectLanesByLaneCodes(Arrays.asList(blnoVslvoyDo.getLaneCodeEx()));
                if(ObjectUtils.isEmpty(laneRespVos)){
                    throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_ENTITY_NOT_EXISTS);
                }else {
                    businessEntity = laneRespVos.get(0).getBusinessEntity();
                }
            }
        }
        if (ObjectUtils.isEmpty(businessEntity)){
            throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_ENTITY_NOT_EXISTS);
        }
        if(!PrintStatusEnum.SNL.getValue().equalsIgnoreCase(businessEntity) && !PrintStatusEnum.HW.getValue().equalsIgnoreCase(businessEntity) && !PrintStatusEnum.HN.getValue().equalsIgnoreCase(businessEntity)){
            throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_ENTITY_NOT_SUPPORT_ERROR);
        }

        if (!ObjectUtils.isEmpty(blnoDO.getBlEntity())){
            return null;
        }else {
            return businessEntity;
        }
    }

    /**
     * 检查刷新打印件条件是否满足
     */
    private void checkFlushPrint(BlnoDO blnoDO,String fileType){

        // 确认件校验
        if (PrintStatusEnum.PRINTBL.getValue().equalsIgnoreCase(fileType)
                || PrintStatusEnum.PREVIEWBL.getValue().equalsIgnoreCase(fileType)) {
            if (ObjectUtils.isEmpty(blnoDO.getBlLockFlag()) || !blnoDO.getBlLockFlag()) {
                throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_LOCK_FLAG_NOT_EXISTS);
            }
            if(!ObjectUtils.isEmpty(blnoDO.getRestrictionFlag()) && blnoDO.getRestrictionFlag()){
                throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_IS_RESTRICTED);
            }
            if (PrintStatusEnum.PRINTBL.getValue().equalsIgnoreCase(fileType)){
                //提单的 Sign Type 必须是空的或者是 B
                if (!(ObjectUtils.isEmpty(blnoDO.getSignType()) || PrintStatusEnum.B.getValue().equalsIgnoreCase(blnoDO.getSignType()))){
                    throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_SIGN_TYPE_ERROR);
                }
            }
            if (PrintStatusEnum.PREVIEWBL.getValue().equalsIgnoreCase(fileType)){
                //提单的 Sign Type 必须非空并且是 T、W、B
                if (!
                        (!ObjectUtils.isEmpty(blnoDO.getSignType())
                                && Arrays.asList(PrintStatusEnum.T.getValue().toUpperCase(),
                                PrintStatusEnum.W.getValue().toUpperCase(),
                                PrintStatusEnum.B.getValue().toUpperCase()).contains(blnoDO.getSignType().toUpperCase()))
                ){
                    throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_NOT_SIGN_OR_ERROR);
                }
            }

        }
    }

    /**
     * 校验草稿打印状态
     */
    private void checkPrintStatus(BlnoDO blnoDO,String fileType){
        //草稿件打印校验
        if (PrintStatusEnum.DRAFTBL.getValue().equalsIgnoreCase(fileType)) {
            boolean check = blnoDO != null && !(SiStatusEnum.BookingPendingSpace.getValue().equalsIgnoreCase(blnoDO.getSiStatus())
                    || SiStatusEnum.AwaitingMaterialUploaded.getValue().equalsIgnoreCase(blnoDO.getSiStatus()));
            if (check) {
                throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_SI_STATUS_NOT_CONFIRMED_DRAFT);
            }
        }
        // 确认件校验
        if (PrintStatusEnum.PRINTBL.getValue().equalsIgnoreCase(fileType)
                || PrintStatusEnum.PREVIEWBL.getValue().equalsIgnoreCase(fileType)) {
            if (ObjectUtils.isEmpty(blnoDO.getBlLockFlag()) || !blnoDO.getBlLockFlag()) {
                throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_LOCK_FLAG_NOT_EXISTS);
            }
            if(!ObjectUtils.isEmpty(blnoDO.getRestrictionFlag()) && blnoDO.getRestrictionFlag()){
                throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_IS_RESTRICTED);
            }
            if(!(SiStatusEnum.BookingConfirmed.getValue().equalsIgnoreCase(blnoDO.getSiStatus())
                    || SiStatusEnum.BookingRejected.getValue().equalsIgnoreCase(blnoDO.getSiStatus()))){
                throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_SI_STATUS_NOT_CONFIRMED);
            }
            if (PrintStatusEnum.PRINTBL.getValue().equalsIgnoreCase(fileType)){
                //提单的 Sign Type 必须是空的或者是 B
                if (!(ObjectUtils.isEmpty(blnoDO.getSignType()) || PrintStatusEnum.B.getValue().equalsIgnoreCase(blnoDO.getSignType()))){
                    throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_SIGN_TYPE_ERROR);
                }
            }
            if (PrintStatusEnum.PREVIEWBL.getValue().equalsIgnoreCase(fileType)){
                //提单的 Sign Type 必须非空并且是 T、W、B
                if (!
                        (!ObjectUtils.isEmpty(blnoDO.getSignType())
                                && Arrays.asList(PrintStatusEnum.T.getValue().toUpperCase(),
                                PrintStatusEnum.W.getValue().toUpperCase(),
                                PrintStatusEnum.B.getValue().toUpperCase()).contains(blnoDO.getSignType().toUpperCase()))
                ){
                    throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_NOT_SIGN_OR_ERROR);
                }
            }

        }
    }



    /**
     * 获取已打印过的文件
     * */
    private File getPrintedFile(BlnoBlPrintLogDO blnoBlPrintLogDO,String fileType){
        String printedBlFileId = this.getPrintedBlFileId(blnoBlPrintLogDO, fileType);
        String printedBlFileName = this.getPrintedBlFileName(blnoBlPrintLogDO, fileType);
        if (!ObjectUtils.isEmpty(printedBlFileId) || !ObjectUtils.isEmpty(printedBlFileName)){
            byte[] fileBinaryDataByFileId = this.getFileBinaryDataByFileId(printedBlFileId);
            File file = new File(FileOperateUtil.getResourcePath() + File.separator + printedBlFileName);
            FileOperateUtil.readBytesFileToFile(fileBinaryDataByFileId,file);
            return file;
        }else {
            return null;
        }
    }

    /**
    * 获取已打印过的文件自己数组
    * */
    private byte[] getPrintedFileBytes(BlnoBlPrintLogDO blnoBlPrintLogDO,String fileType){
        String printedBlFileId = this.getPrintedBlFileId(blnoBlPrintLogDO, fileType);
        if (!ObjectUtils.isEmpty(printedBlFileId)){
            return this.getFileBinaryDataByFileId(printedBlFileId);
        }else {
            return null;
        }
    }

    private String getPrintedBlFileId(BlnoBlPrintLogDO blnoBlPrintLogDO,String fileType){
        if (PrintStatusEnum.DRAFTBL.getValue().equalsIgnoreCase(fileType) && !ObjectUtils.isEmpty(blnoBlPrintLogDO.getDraftFileId())){
            return blnoBlPrintLogDO.getDraftFileId();
        }else if(PrintStatusEnum.PREVIEWBL.getValue().equalsIgnoreCase(fileType) && !ObjectUtils.isEmpty(blnoBlPrintLogDO.getPreviewFileId())){
            return blnoBlPrintLogDO.getPreviewFileId();
        }else if(PrintStatusEnum.PRINTBL.getValue().equalsIgnoreCase(fileType) && !ObjectUtils.isEmpty(blnoBlPrintLogDO.getIssueFieId())){
            return blnoBlPrintLogDO.getIssueFieId();
        }else if(PrintStatusEnum.PRINTATT.getValue().equalsIgnoreCase(fileType) && !ObjectUtils.isEmpty(blnoBlPrintLogDO.getAttFileId())){
            return blnoBlPrintLogDO.getAttFileId();
        }else {
            return null;
        }
    }
    private String getPrintedBlFileName(BlnoBlPrintLogDO blnoBlPrintLogDO,String fileType){
        if (PrintStatusEnum.DRAFTBL.getValue().equalsIgnoreCase(fileType) && !ObjectUtils.isEmpty(blnoBlPrintLogDO.getDraftFileId())){
            return blnoBlPrintLogDO.getDraftFileName();
        }else if(PrintStatusEnum.PREVIEWBL.getValue().equalsIgnoreCase(fileType) && !ObjectUtils.isEmpty(blnoBlPrintLogDO.getPreviewFileId())){
            return blnoBlPrintLogDO.getPreviewFileName();
        }else if(PrintStatusEnum.PRINTBL.getValue().equalsIgnoreCase(fileType) && !ObjectUtils.isEmpty(blnoBlPrintLogDO.getIssueFieId())){
            return blnoBlPrintLogDO.getIssueFileName();
        }else if(PrintStatusEnum.PRINTATT.getValue().equalsIgnoreCase(fileType) && !ObjectUtils.isEmpty(blnoBlPrintLogDO.getAttFileId())){
            return blnoBlPrintLogDO.getAttFileName();
        }else {
            return null;
        }
    }


    private Map<String,File> printBlNoFile(BlnoDO blnoDO,String fileType,String blEntity,boolean existAtt){
        HashMap<String, File> stringFilePath = new HashMap<>(16);
        String entity = ObjectUtils.isEmpty(blnoDO.getBlEntity())?blEntity:blnoDO.getBlEntity();
        //预加载打印数据，预防打印
        if (PrintStatusEnum.PRINTBL.getValue().equalsIgnoreCase(fileType) && existAtt) {
            //打印纸质无背景时且有附件首页和附件页分开打印
            String resourcePath = FileOperateUtil.getResourcePath();
            String firstPagePath = resourcePath + File.separator + blnoDO.getId() + System.currentTimeMillis() + fileType.toUpperCase() + PDF;
            File firstPageFile = new File(firstPagePath);
            this.printBlNoFile(blnoDO.getId(), fileType, firstPageFile,entity,blnoDO.getSignType(),false);
            stringFilePath.put(FIRST_PAGE, firstPageFile);
            String secondPagePath = resourcePath + File.separator + blnoDO.getId() + System.currentTimeMillis() + PrintStatusEnum.PRINTATT.getValue().toUpperCase() + PDF;
            File secondPageFile = new File(secondPagePath);
            this.printBlNoFile(blnoDO.getId(), PrintStatusEnum.PRINTATT.getValue(), secondPageFile,entity,blnoDO.getSignType(),false);
            stringFilePath.put(SECOND_PAGE, secondPageFile);
        }else {
            String resourcePath = FileOperateUtil.getResourcePath();
            String firstPagePath = resourcePath + File.separator + blnoDO.getId() + System.currentTimeMillis() + fileType + PDF;
            File firstPageFile = new File(firstPagePath);
            this.printBlNoFile(blnoDO.getId(), fileType, firstPageFile,entity,blnoDO.getSignType(),existAtt);
            stringFilePath.put(FIRST_PAGE, firstPageFile);
        }
        return stringFilePath;
    }

    private void printBlNoFile(Long blNoId,String fileType,File targetFile,String blEntity,String signType,boolean existAtt){
        if (PrintStatusEnum.DRAFTBL.getValue().equalsIgnoreCase(fileType)){
            FineReportUrlReqVO fineReportUrlReqV = new FineReportUrlReqVO();
            fineReportUrlReqV.setInputUrl(url+PrintBlnoUrlEnum.DRAFTURL.getUrl());
            FineReportUrlRespVO fineReportUrlRespVO = fineReportUrlService.processUrl(fineReportUrlReqV);
            StringBuilder urlString = new StringBuilder(fineReportUrlRespVO.getProcessUrl());
            this.appendUrl(urlString,blNoId,existAtt);
            this.downloadFile(urlString.toString(),targetFile);
        }
        if (PrintStatusEnum.PREVIEWBL.getValue().equalsIgnoreCase(fileType)){
            FineReportUrlReqVO fineReportUrlReqV = new FineReportUrlReqVO();
            if (PrintStatusEnum.HN.getValue().equalsIgnoreCase(blEntity) && PrintStatusEnum.B.getValue().equalsIgnoreCase(signType)){
                //海南 正本电子
                fineReportUrlReqV.setInputUrl(url+PrintBlnoUrlEnum.PREVIEWURL_HN_B.getUrl());
            }else if (PrintStatusEnum.HW.getValue().equalsIgnoreCase(blEntity) && PrintStatusEnum.B.getValue().equalsIgnoreCase(signType)){
                //海外 正本电子
                fineReportUrlReqV.setInputUrl(url+PrintBlnoUrlEnum.PREVIEWURL_HW_B.getUrl());
            }else if (PrintStatusEnum.SNL.getValue().equalsIgnoreCase(blEntity) && PrintStatusEnum.B.getValue().equalsIgnoreCase(signType)){
                //集运 正本电子
                fineReportUrlReqV.setInputUrl(url+PrintBlnoUrlEnum.PREVIEWURL_SNL_B.getUrl());
            } else if (PrintStatusEnum.HN.getValue().equalsIgnoreCase(blEntity) && PrintStatusEnum.W.getValue().equalsIgnoreCase(signType)){
                //海南 海运
                fineReportUrlReqV.setInputUrl(url+PrintBlnoUrlEnum.PREVIEWURL_HN_W.getUrl());
            }else if (PrintStatusEnum.HN.getValue().equalsIgnoreCase(blEntity) && PrintStatusEnum.T.getValue().equalsIgnoreCase(signType)){
                //海南 电放
                fineReportUrlReqV.setInputUrl(url+PrintBlnoUrlEnum.PREVIEWURL_HN_T.getUrl());
            }else if (PrintStatusEnum.HW.getValue().equalsIgnoreCase(blEntity) && PrintStatusEnum.W.getValue().equalsIgnoreCase(signType)){
                //海外 海运
                fineReportUrlReqV.setInputUrl(url+PrintBlnoUrlEnum.PREVIEWURL_HW_W.getUrl());
            }else if (PrintStatusEnum.HW.getValue().equalsIgnoreCase(blEntity) && PrintStatusEnum.T.getValue().equalsIgnoreCase(signType)){
                //海外 电放
                fineReportUrlReqV.setInputUrl(url+PrintBlnoUrlEnum.PREVIEWURL_HW_T.getUrl());
            }else if (PrintStatusEnum.SNL.getValue().equalsIgnoreCase(blEntity) && PrintStatusEnum.W.getValue().equalsIgnoreCase(signType)){
                //集运 海运
                fineReportUrlReqV.setInputUrl(url+PrintBlnoUrlEnum.PREVIEWURL_SNL_W.getUrl());
            }else if (PrintStatusEnum.SNL.getValue().equalsIgnoreCase(blEntity) && PrintStatusEnum.T.getValue().equalsIgnoreCase(signType)){
                //集运 电放
                fineReportUrlReqV.setInputUrl(url+PrintBlnoUrlEnum.PREVIEWURL_SNL_T.getUrl());
            }
            FineReportUrlRespVO fineReportUrlRespVO = fineReportUrlService.processUrl(fineReportUrlReqV);
            StringBuilder urlString = new StringBuilder(fineReportUrlRespVO.getProcessUrl());
            this.appendUrl(urlString,blNoId,existAtt);
            this.downloadFile(urlString.toString(),targetFile);
        }
        if (PrintStatusEnum.PRINTBL.getValue().equalsIgnoreCase(fileType)){
            FineReportUrlReqVO fineReportUrlReqV = new FineReportUrlReqVO();
            fineReportUrlReqV.setInputUrl(url+PrintBlnoUrlEnum.PRINTBLURL.getUrl());
            FineReportUrlRespVO fineReportUrlRespVO = fineReportUrlService.processUrl(fineReportUrlReqV);
            StringBuilder urlString = new StringBuilder(fineReportUrlRespVO.getProcessUrl());
            this.appendUrl(urlString,blNoId,existAtt);
            this.downloadFile(urlString.toString(),targetFile);
        }
        if (PrintStatusEnum.PRINTATT.getValue().equalsIgnoreCase(fileType)){
            FineReportUrlReqVO fineReportUrlReqV = new FineReportUrlReqVO();
            fineReportUrlReqV.setInputUrl(url+PrintBlnoUrlEnum.PRINTBLATTURL.getUrl());
            FineReportUrlRespVO fineReportUrlRespVO = fineReportUrlService.processUrl(fineReportUrlReqV);
            StringBuilder urlString = new StringBuilder(fineReportUrlRespVO.getProcessUrl());
            this.appendUrl(urlString,blNoId,false);
            this.downloadFile(urlString.toString(),targetFile);
        }

    }

    private void appendUrl(StringBuilder urlString,Long blNoId,boolean existAtt){
        urlString.append("&blId=").append(blNoId).append("&format=pdf").append("&tag=").append(tag);
        if (!existAtt){
            urlString.append("&sheets=[0]");
        }
    }


    private void downloadFile(String url,File tragetFile){
        if (tragetFile.exists()){
            boolean delete = tragetFile.delete();
        }
        FileOperateUtil.downloadWithCommonsIo(url,tragetFile);
    }

    private void uploadFileAndSavePrintData(BlnoDO blnoDO,Map<String,File> stringFile,String fileType,BlnoMaintenanceInfoRespVO blnoMaintenanceInfo){
        File secondPage = stringFile.get(SECOND_PAGE);
        boolean isExistAtt = false;
        if (!ObjectUtils.isEmpty(secondPage) && secondPage.exists() && secondPage.length() > 0){
            isExistAtt = true;
        }

        File firstPage = stringFile.get(FIRST_PAGE);
        if (!ObjectUtils.isEmpty(firstPage) && firstPage.exists() && firstPage.length() > 0){
            String fileName = blnoDO.getBlNo() + "_" + replaceHyphenWithUnderscore(fileType.toUpperCase()) + "_" + LocalDateTime.now().format(FORMATTER_AMEND) + PDF;
            FileUploadRespDTO fileUploadRespDTO = this.cmclinkSystemUpload(firstPage, fileName, FILE_BUSINESS_POINT);
            if (!ObjectUtils.isEmpty(fileUploadRespDTO)){
                if (isExistAtt){
                    String attFileName = blnoDO.getBlNo() + "_" + replaceHyphenWithUnderscore(PrintStatusEnum.PRINTATT.getValue().toUpperCase()) + "_" + LocalDateTime.now().format(FORMATTER_AMEND) + PDF;
                    FileUploadRespDTO attFileUploadRespDTO = this.cmclinkSystemUpload(secondPage, attFileName, FILE_BUSINESS_POINT);
                    if (!ObjectUtils.isEmpty(fileUploadRespDTO)){
                        //保存提单打印数据
                        savePrintLogData(blnoDO,fileType,String.valueOf(fileUploadRespDTO.getId()),fileUploadRespDTO.getFileName(),attFileUploadRespDTO.getId(),attFileUploadRespDTO.getFileName(),blnoMaintenanceInfo);
                    }
                }else {
                    //保存提单打印数据
                    savePrintLogData(blnoDO,fileType,String.valueOf(fileUploadRespDTO.getId()),fileUploadRespDTO.getFileName(),null,null,blnoMaintenanceInfo);
                }
            }
        }else {
            //打印文件为空
            throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_PRINT_FILE_IS_EMPTY);
        }
    }

    private void updateBlno(BlnoDO blnoDO,String fileType,String blEntity){
        LambdaUpdateWrapper<BlnoDO> blnoDoLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        blnoDoLambdaUpdateWrapper.eq(BlnoDO::getId,blnoDO.getId());
        boolean updateFlag = false;
        if (!ObjectUtils.isEmpty(blEntity)){
            blnoDoLambdaUpdateWrapper.set(BlnoDO::getBlEntity,blEntity);
            updateFlag = true;
        }
        if (PrintStatusEnum.DRAFTBL.getValue().equalsIgnoreCase(fileType)) {
            blnoDoLambdaUpdateWrapper.set(BlnoDO::getSiStatus,SiStatusEnum.AwaitingMaterialUploaded.getValue());
            updateFlag = true;
        }
        if (PrintStatusEnum.PREVIEWBL.getValue().equalsIgnoreCase(fileType) || PrintStatusEnum.PRINTBL.getValue().equalsIgnoreCase(fileType)) {
            blnoDoLambdaUpdateWrapper.set(BlnoDO::getSiStatus,SiStatusEnum.BookingRejected.getValue());
            updateFlag = true;
        }
        if (updateFlag){
            blnoMapper.update(null,blnoDoLambdaUpdateWrapper);
        }
    }

    @Override
    public void updateBlEntiyt(BlnoDO blnoDO,String blEntity){
        LambdaUpdateWrapper<BlnoDO> blnoDoLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        blnoDoLambdaUpdateWrapper.eq(BlnoDO::getId,blnoDO.getId());
        blnoDoLambdaUpdateWrapper.set(BlnoDO::getBlEntity,blEntity);
        blnoMapper.update(null,blnoDoLambdaUpdateWrapper);
    }


    private void savePrintLogData(@NotNull BlnoDO blnoDO,@NotNull String fileType,@NotNull String fileId,@NotNull String fileName,Long attFileId,String attFileName,BlnoMaintenanceInfoRespVO blnoMaintenanceInfoRespVO){
        String jsonString = JSONObject.toJSONString(blnoMaintenanceInfoRespVO);
        String ladenOnBoard = blnoMaintenanceInfoRespVO.getLadenOnBoard();
        BlnoBlPrintLogDO blnoBlPrintLogDO = blnoBlPrintLogMapper.selectOne(new LambdaQueryWrapper<BlnoBlPrintLogDO>().eq(BlnoBlPrintLogDO::getBlnoId, blnoDO.getId()));
        if (!ObjectUtils.isEmpty(blnoBlPrintLogDO) && !ObjectUtils.isEmpty(blnoBlPrintLogDO.getId())){
            LambdaUpdateWrapper<BlnoBlPrintLogDO> blnoBlPrintLogDoLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
            blnoBlPrintLogDoLambdaUpdateWrapper.eq(BlnoBlPrintLogDO::getBlnoId,blnoBlPrintLogDO.getBlnoId());
            if (!ObjectUtils.isEmpty(ladenOnBoard)){
                blnoBlPrintLogDoLambdaUpdateWrapper.set(BlnoBlPrintLogDO::getLadenOnBoard,ladenOnBoard);
            }
            if (PrintStatusEnum.DRAFTBL.getValue().equalsIgnoreCase(fileType)) {
                blnoBlPrintLogDoLambdaUpdateWrapper.set(BlnoBlPrintLogDO::getDraftFileId,fileId);
                blnoBlPrintLogDoLambdaUpdateWrapper.set(BlnoBlPrintLogDO::getDraftFileName,fileName);
                blnoBlPrintLogDoLambdaUpdateWrapper.set(BlnoBlPrintLogDO::getPrintJson,jsonString);
            } else if (PrintStatusEnum.PREVIEWBL.getValue().equalsIgnoreCase(fileType)) {
                blnoBlPrintLogDoLambdaUpdateWrapper.set(BlnoBlPrintLogDO::getPreviewFileId,fileId);
                blnoBlPrintLogDoLambdaUpdateWrapper.set(BlnoBlPrintLogDO::getPreviewFileName,fileName);
                if (ObjectUtils.isEmpty(blnoBlPrintLogDO.getPrintJson())){
                    //打印preview和printbl时，如果json已存在则无需更新json
                    blnoBlPrintLogDoLambdaUpdateWrapper.set(BlnoBlPrintLogDO::getPrintJson,jsonString);
                }
            }else if(PrintStatusEnum.PRINTBL.getValue().equalsIgnoreCase(fileType)){
                blnoBlPrintLogDoLambdaUpdateWrapper.set(BlnoBlPrintLogDO::getIssueFieId,fileId);
                blnoBlPrintLogDoLambdaUpdateWrapper.set(BlnoBlPrintLogDO::getIssueFileName,fileName);
                if (ObjectUtils.isEmpty(blnoBlPrintLogDO.getPrintJson())){
                    //打印preview和printbl时，如果json已存在则无需更新json
                    blnoBlPrintLogDoLambdaUpdateWrapper.set(BlnoBlPrintLogDO::getPrintJson,jsonString);
                }
                if (!ObjectUtils.isEmpty(attFileId) && !ObjectUtils.isEmpty(attFileName)){
                    blnoBlPrintLogDoLambdaUpdateWrapper.set(BlnoBlPrintLogDO::getAttFileId,attFileId);
                    blnoBlPrintLogDoLambdaUpdateWrapper.set(BlnoBlPrintLogDO::getAttFileName,attFileName);
                }
            }
            int update = blnoBlPrintLogMapper.update(null, blnoBlPrintLogDoLambdaUpdateWrapper);
        }else {
            BlnoBlPrintLogDO insertBlnoBlPrintLogDO = new BlnoBlPrintLogDO();
            insertBlnoBlPrintLogDO.setBlnoId(blnoDO.getId());
            insertBlnoBlPrintLogDO.setBlno(blnoDO.getBlNo());
            insertBlnoBlPrintLogDO.setPrintJson(jsonString);
            if (!ObjectUtils.isEmpty(ladenOnBoard)){
                insertBlnoBlPrintLogDO.setLadenOnBoard(ladenOnBoard);
            }
            if (PrintStatusEnum.DRAFTBL.getValue().equalsIgnoreCase(fileType)) {
                insertBlnoBlPrintLogDO.setDraftFileId(fileId);
                insertBlnoBlPrintLogDO.setDraftFileName(fileName);
            } else if (PrintStatusEnum.PREVIEWBL.getValue().equalsIgnoreCase(fileType)) {
                insertBlnoBlPrintLogDO.setPreviewFileId(fileId);
                insertBlnoBlPrintLogDO.setPreviewFileName(fileName);
            }else if(PrintStatusEnum.PRINTBL.getValue().equalsIgnoreCase(fileType)){
                insertBlnoBlPrintLogDO.setIssueFieId(fileId);
                insertBlnoBlPrintLogDO.setIssueFileName(fileName);
                if (!ObjectUtils.isEmpty(attFileId) && !ObjectUtils.isEmpty(attFileName)){
                    insertBlnoBlPrintLogDO.setAttFileId(fileId);
                    insertBlnoBlPrintLogDO.setAttFileName(fileName);
                }
            }
            blnoBlPrintLogMapper.insert(insertBlnoBlPrintLogDO);
        }


    }

    private FileUploadRespDTO cmclinkSystemUpload(File file,String fileName,String fileBusinessPoint){
        try {
            FileUploadReqDTO fileUploadReqDTO = getFileUploadReqDTO(file, fileName, fileBusinessPoint);
            CommonResult<FileUploadRespDTO> uploadResp = fileApi.upload(fileUploadReqDTO);
            return uploadResp.getCheckedData();
        } catch (IOException e) {
            log.error(e.getMessage(),e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取文件二进制数据
     */
    public static @NotNull FileUploadReqDTO getFileUploadReqDTO(byte[] fileBytes, String fileName, String fileBusinessPoint) {
        MultipartFile multipartFile = new ByteToMultipartFile(fileBytes, FILE, fileName, CONTENT_TYPE);
        FileUploadReqDTO fileUploadReqDTO = new FileUploadReqDTO();
        fileUploadReqDTO.setFileName(fileName);
        fileUploadReqDTO.setFile(multipartFile);
        fileUploadReqDTO.setInnerServiceName(PrintStatusEnum.INNER_SERVICE_NAME);
        fileUploadReqDTO.setFileBusinessPoint(fileBusinessPoint);
        return fileUploadReqDTO;
    }

    /**
     * 获取文件二进制数据
     */
    public static @NotNull FileUploadReqDTO getFileUploadReqDTO(File file, String fileName, String fileBusinessPoint) {
        try {
            MultipartFile multipartFile = new ByteToMultipartFile(Files.readAllBytes(Paths.get(file.getAbsolutePath())), FILE, fileName, CONTENT_TYPE);
            FileUploadReqDTO fileUploadReqDTO = new FileUploadReqDTO();
            fileUploadReqDTO.setFileName(fileName);
            fileUploadReqDTO.setFile(multipartFile);
            fileUploadReqDTO.setInnerServiceName(PrintStatusEnum.INNER_SERVICE_NAME);
            fileUploadReqDTO.setFileBusinessPoint(fileBusinessPoint);
            return fileUploadReqDTO;
        } catch (IOException e) {
            log.error(e.getMessage(),e);
            throw new RuntimeException(e);
        }

    }

    @Override
    public BlnoBlPrintLogDO getBlnoBlPrintLogPreview(Long blnoId) {
        // 根据提单ID查询打印记录
        return blnoBlPrintLogMapper.selectOne(new LambdaQueryWrapper<BlnoBlPrintLogDO>()
                .eq(BlnoBlPrintLogDO::getBlnoId, blnoId)
                .orderByDesc(BlnoBlPrintLogDO::getCreateTime)
                .last("LIMIT 1"));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public BlnoMaintenanceInfoRespVO getBlnoMaintenanceInfo(Long blnoId,String printType) {
        // 1. 查询提单主信息
        LambdaQueryWrapper<BlnoDO> blnoQuery = new LambdaQueryWrapper<>();
        blnoQuery.eq(BlnoDO::getId, blnoId).eq(BlnoDO::getValid, SiValidEnum.VALID.getValue());
        // 目的港
        BlnoDO blnoDO = blnoMapper.selectOne(blnoQuery);

        if (blnoDO == null) {
            throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_BL_PRINT_LOG_NOT_EXISTS);
        }
        return this.getBlnoMaintenanceInfo(blnoDO,printType);
    }

    private BlnoMaintenanceInfoRespVO getBlnoMaintenanceInfo(@NotNull BlnoDO blnoDO, String printType) {
        // 创建响应对象
        BlnoMaintenanceInfoRespVO response = new BlnoMaintenanceInfoRespVO();
        //返回si状态，前端需要状态控制是否可编辑
        response.setSiStatus(blnoDO.getSiStatus());

        BlnoBlPrintLogDO blnoBlPrintLogDO = blnoBlPrintLogMapper.selectOne(new LambdaQueryWrapper<BlnoBlPrintLogDO>().eq(BlnoBlPrintLogDO::getBlnoId, blnoDO.getId()).eq(BlnoBlPrintLogDO::getDeleted, false));
        if (blnoBlPrintLogDO != null && blnoBlPrintLogDO.getPrintJson() != null && !blnoBlPrintLogDO.getPrintJson().isEmpty()) {
            String printJson = blnoBlPrintLogDO.getPrintJson();
            try {
                BlnoMaintenanceInfoRespVO blnoMaintenanceInfoRespVO = JSONObject.parseObject(printJson, BlnoMaintenanceInfoRespVO.class);
                if (blnoMaintenanceInfoRespVO != null) {
                    if (ObjectUtils.isEmpty(blnoBlPrintLogDO.getLadenOnBoard())){
                        LambdaQueryWrapper<BlnoVslvoyDO> vslvoyQuery = new LambdaQueryWrapper<>();
                        vslvoyQuery.eq(BlnoVslvoyDO::getBlnoId, blnoDO.getId());
                        List<BlnoVslvoyDO> vslvoyList = blnoVslvoyMapper.selectList(vslvoyQuery);
                        BlnoVslvoyDO printVslvoy = this.getPrintVslvoy(blnoDO, vslvoyList);
                        String ladenOnBoard = this.getLadenOnBoard(blnoDO, printVslvoy);
                        blnoMaintenanceInfoRespVO.setLadenOnBoard(ladenOnBoard);
                    }else {
                        blnoMaintenanceInfoRespVO.setLadenOnBoard(blnoBlPrintLogDO.getLadenOnBoard());
                    }
                    response = blnoMaintenanceInfoRespVO;
                    //返回si状态，前端需要状态控制是否可编辑
                    response.setSiStatus(blnoDO.getSiStatus());
                }
            } catch (Exception e) {
                throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_JSON_PARSE_ERROR);
            }
        }else {

            response.setBlNo(blnoDO.getBlNo());
            response.setBookingNo(blnoDO.getBookingNo());
            response.setReferenceNo(blnoDO.getReferenceNo());
            response.setSiStatus(blnoDO.getSiStatus());

            // 查询公司信息（从doc_blno_scn表）
            LambdaQueryWrapper<BlnoScnDO> scnQuery = new LambdaQueryWrapper<>();
            scnQuery.eq(BlnoScnDO::getBlnoId, blnoDO.getId());
            List<BlnoScnDO> scnDOList = blnoScnMapper.selectList(scnQuery);
            BlnoScnDO shipper = scnDOList.stream()
                    .filter(x -> PartyTypeCodeEnum.SHIPPER.getValue().equals(x.getPartyTypeCode()))
                    .findFirst()
                    .orElse(null);
            //1. Shipper 发货人
            this.setShipper(response,shipper);
            BlnoScnDO consignee = scnDOList.stream()
                    .filter(x -> PartyTypeCodeEnum.CONSIGNEE.getValue().equals(x.getPartyTypeCode()))
                    .findFirst()
                    .orElse(null);
            //2. Consignee 收货人
            this.setConsignee(response,consignee);
            BlnoScnDO notify = scnDOList.stream()
                    .filter(x -> PartyTypeCodeEnum.NOTIFY.getValue().equals(x.getPartyTypeCode()))
                    .findFirst()
                    .orElse(null);
            //Notify 通知人
            this.setNotify(response,notify);
            //4.Pre-carriage By* 目前为空

            //5.Place of Receipt*  接货地描述	字段： por_desc
            response.setPlaceOfReceipt(Optional.ofNullable(blnoDO.getPorDesc())
                    .map(String::toUpperCase)
                    .orElse(""));

            LambdaQueryWrapper<BlnoVslvoyDO> vslvoyQuery = new LambdaQueryWrapper<>();
            vslvoyQuery.eq(BlnoVslvoyDO::getBlnoId, blnoDO.getId());
            List<BlnoVslvoyDO> vslvoyList = blnoVslvoyMapper.selectList(vslvoyQuery);
            BlnoVslvoyDO printVslvoy = this.getPrintVslvoy(blnoDO, vslvoyList);
            //6. Vessel & Voyage No. 如果【签发小船提单】=Y 则打印一程的船名航次。否则获取doc_blno_vslvoy 表的 POL=doc_blno 表的current_port 的那段的船名航次
            this.setVesselVoyageNo(response,printVslvoy);
            // 7.Port of Loading
            this.setPortOfLoading(response,blnoDO);
            //8.Port of Discharge
            this.setPortOfDischarge(response,blnoDO);
            //9.Place of Delivery*
            response.setPlaceOfDelivery(Optional.ofNullable(blnoDO.getDelDesc())
                    .map(String::toUpperCase)
                    .orElse(""));
            //10.Point and Country of Origin 目前为空

            //11.Marks & Nos. Container/Seal No.
            //查询货物信息
            List<BlnoCargoDO> cargoList = blnoCargoMapper.selectList(new LambdaQueryWrapper<BlnoCargoDO>()
                    .eq(BlnoCargoDO::getBlnoId, blnoDO.getId()));
            BlnoCargoDO blnoCargoDO = null;
            if (!CollectionUtils.isEmpty(cargoList)) {
                blnoCargoDO = cargoList.get(0);
            }
            // 查询集装箱信息
            List<BlnoCntrDO> cntrList = blnoCntrMapper.selectList(new LambdaQueryWrapper<BlnoCntrDO>()
                    .eq(BlnoCntrDO::getBlnoId, blnoDO.getId())
            );

            this.setMarksAndContainers(response,blnoCargoDO,cntrList,blnoDO);
            //No.of Packages or Containers
            if (blnoCargoDO != null) {
                response.setPackagesContainers(blnoCargoDO.getQuantity() + "  " + Optional.ofNullable(blnoCargoDO.getPackageDesc())
                        .map(String::toUpperCase).orElse(""));
            }
            // Description of Goods	超长情况 5000-8000字
            if (blnoCargoDO != null) {
                response.setDescriptionOfGoods(Optional.ofNullable(blnoCargoDO.getCommodityEn())
                        .map(String::toUpperCase)
                        .orElse(""));
            }
            // 温度通风度
            StringBuilder temperature = new StringBuilder();
            StringBuilder dangerousGoods = new StringBuilder();
            StringBuilder boxList = new StringBuilder();
            boolean check1 = blnoCargoDO != null && (RF.equals(blnoCargoDO.getCargoType()) || RD.equals(blnoCargoDO.getCargoType()));
            if (check1) {
                temperature = new StringBuilder();
                temperature.append(TEMP).append(padRight(Optional.ofNullable(blnoCargoDO.getTemperature())
                        .map(String::toUpperCase)
                        .orElse("") + blnoCargoDO.getTemperatureUnit(), 7));
                temperature.append(VENT).append(blnoCargoDO.getVantilation());
            }
            response.setTemperatureVentilation(temperature.toString());

            // 危险品
            boolean check2 = blnoCargoDO != null && (RD.equals(blnoCargoDO.getCargoType()) || DG.equals(blnoCargoDO.getCargoType()));
            if (check2) {
                if (StringUtils.isNotBlank(blnoCargoDO.getUnNo())) {
                    dangerousGoods.append(UNNO).append(padRight(Optional.ofNullable(blnoCargoDO.getUnNo())
                            .map(String::toUpperCase)
                            .orElse(""), 8));
                    if (StringUtils.isNotBlank(blnoCargoDO.getImoClass())) {
                        dangerousGoods.append(CLASS).append(blnoCargoDO.getImoClass());
                    }
                }
            }
            response.setDangerousGoods(dangerousGoods.toString());
            // 拼箱提单号列表
            if (!CollectionUtils.isEmpty(cntrList)) {
                List<String> lclBlNos = Collections.emptyList();
                for (BlnoCntrDO cntr : cntrList) {
                    lclBlNos = getLclBlNos(blnoDO.getBkgVesselCode(), blnoDO.getBkgVoyage(), blnoDO.getBlNo(), cntr.getCntrNo());
                    if (!lclBlNos.isEmpty()) {
                        // 拼接拼箱提单号，用/隔开
                        String joinedBlNos = String.join("/", lclBlNos);
                        boxList.append(joinedBlNos);
                    }
                }
                if(lclBlNos!=null && !lclBlNos.isEmpty()){
                    response.setLclBlNos("ONLY DELIVERY AFTER RECEIVING ALL RELATED ORGINAL B/LS TO THE CONTAINER RECEIVED.\n" + boxList);
                }
            }
            //Gross Weight(Kgs)
            if (blnoCargoDO != null) {
                // 格式化毛重，确保有3位小数
                DecimalFormat df = new DecimalFormat("0.000");
                String formattedGrossWeight = df.format(blnoCargoDO.getGrossWeight()) + "KGS\n";
                response.setGrossWeight(formattedGrossWeight);
            }
            //Measurement(Cbm)
            if (blnoCargoDO != null) {
                // 格式化体积，确保有3位小数
                DecimalFormat df = new DecimalFormat("0.000");
                String formattedMeasurement = df.format(blnoCargoDO.getMeasurement()) + "CBM";
                response.setMeasurement(formattedMeasurement);
            }
            //12.Total Number of Packages or Container (in Words)
            this.setTotalPackagesInWords(response,cntrList,blnoDO);

            CommonResult<List<DictDataRespDTO>> dictDataList = dictDataApi.getDictDataList("doc_frt_payment");
            LambdaQueryWrapper<BlnoPaymentDO> paymentQuery = new LambdaQueryWrapper<>();
            paymentQuery.eq(BlnoPaymentDO::getBlnoId, blnoDO.getId());
            List<BlnoPaymentDO> paymentDO = blnoPaymentMapper.selectList(paymentQuery);
            if (dictDataList != null && !ObjectUtils.isEmpty(dictDataList.getData())) {
                // 13 附加费 运费和费用
                this.setFreightCharges(response,paymentDO,dictDataList.getData());
                //14 海运费付款方式
                this.setPrepaidCollect(response,paymentDO,dictDataList.getData());
            }else {
                throw ServiceExceptionUtil.exception(RpcErrorCodeConstants.QUERY_DICT_ERROR);
            }
            //15 付费地
            this.setPrepaidAt(response,blnoDO);
            //16.Payable at		空
            //17.Total Prepaid
            //18 装船日期和时间
            this.setLadenOnBoard(response,blnoDO,printVslvoy);
            //19  取print_bl_number表的print_bl_number,表里存储的是 1、2、3 要转换,1 份打印成： ONE(1)
            if (blnoDO.getPrintBlNumber() != null) {
                this.setOriginalBlNumber(response,blnoDO.getPrintBlNumber());
            }
            //20 签发地、签发日期
            this.setPlaceDateOfIssue(response,blnoDO);
            //21 目的港联系方式
            this.setTranshipmentContact(response,blnoDO,vslvoyList);

            //处理附件附件页
            this.setAttachment(response,printType);
        }
        return response;
    }

//    private void savePrintLog(BlnoDO blnoDO,BlnoMaintenanceInfoRespVO blnoMaintenanceInfoRespVO){
//        String jsonString = JSONObject.toJSONString(blnoMaintenanceInfoRespVO);
//        LambdaQueryWrapperX<BlnoBlPrintLogDO> blnoBlPrintLogDOLambdaQueryWrapperX = new LambdaQueryWrapperX<>();
//        blnoBlPrintLogDOLambdaQueryWrapperX.eq(BlnoBlPrintLogDO::getBlnoId,blnoDO.getId()).eq(BlnoBlPrintLogDO::getDeleted,0);
//        if (blnoBlPrintLogMapper.exists(blnoBlPrintLogDOLambdaQueryWrapperX)){
//            LambdaUpdateWrapper<BlnoBlPrintLogDO> blnoBlPrintLogDoLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
//            blnoBlPrintLogDoLambdaUpdateWrapper.eq(BlnoBlPrintLogDO::getBlnoId,blnoDO.getId()).eq(BlnoBlPrintLogDO::getDeleted,0);
//            blnoBlPrintLogDoLambdaUpdateWrapper.set(BlnoBlPrintLogDO::getPrintJson,jsonString);
//            blnoBlPrintLogMapper.update(null,blnoBlPrintLogDoLambdaUpdateWrapper);
//        }else {
//            BlnoBlPrintLogDO blnoBlPrintLogDO = new BlnoBlPrintLogDO();
//            blnoBlPrintLogDO.setBlnoId(blnoDO.getId());
//            blnoBlPrintLogDO.setBlno(blnoDO.getBlNo());
//            blnoBlPrintLogDO.setPrintJson(jsonString);
//            blnoBlPrintLogMapper.insert(blnoBlPrintLogDO);
//        }
//    }


    private BlnoVslvoyDO getPrintVslvoy( @NotNull BlnoDO blnoDO, List<BlnoVslvoyDO> vslvoyList){
        BlnoVslvoyDO vslvoy = null;
        if (blnoDO.getIssueFeederBl()) {
            // 分提单逻辑: doc_blno和doc_blno_vslvoy表一对多关系
            if (!CollectionUtils.isEmpty(vslvoyList)) {
                // 筛选vslvoyList中routeNo为1的船期信息
                vslvoy = vslvoyList.stream()
                        .filter(item -> 1 == item.getRouteNo())
                        .limit(1)
                        .findFirst()
                        .orElse(null);
            }
        } else {
            // 主提单逻辑: 查找polCode等于currentPort的船期信息
            String currentPort = blnoDO.getCurrentPort();
            if (currentPort != null) {
                vslvoy = vslvoyList.stream().filter(blnoVslvoyDO -> currentPort.equals(blnoVslvoyDO.getPolCode())).findFirst().orElse(null);
            }
            // 如果没找到匹配的，取第一条
            if (vslvoy == null && !CollectionUtils.isEmpty(vslvoyList)) {
                vslvoy = vslvoyList.get(0);
            }
        }
        return vslvoy;
    }
    private void setShipper(BlnoMaintenanceInfoRespVO response,BlnoScnDO shipper){
        StringBuilder shipperString = new StringBuilder();
        if (shipper != null) {
            shipperString.append(Optional.ofNullable(shipper.getCompanyName())
                    .map(String::toUpperCase)
                    .orElse("")).append(System.lineSeparator());
        }
        // 公司地址（根据打印标识）
        if (shipper != null && shipper.getAddressPrintFlag() != null && shipper.getAddressPrintFlag()) {
            shipperString.append(Optional.ofNullable(shipper.getCompanyAddress())
                    .map(String::toUpperCase)
                    .orElse("")).append(System.lineSeparator());
        }
        // 电话（根据打印标识）
        if (shipper != null && shipper.getTelephonePrintFlag() != null &&shipper.getTelephonePrintFlag()) {
            shipperString.append(TEL)
                    .append(Optional.ofNullable(shipper.getTelephone())
                    .map(String::toUpperCase)
                    .orElse("")).append(System.lineSeparator());
        }
        // 邮箱（根据打印标识）
        if (shipper != null && shipper.getEmailPrintFlag() != null && shipper.getEmailPrintFlag()) {
            shipperString.append(EMAIL).append(Optional.ofNullable(shipper.getEmail())
                    .map(String::toUpperCase)
                    .orElse("")).append(System.lineSeparator());
        }
        // 传真（根据打印标识）
        if (shipper != null && shipper.getFaxPrintFlag() != null && shipper.getFaxPrintFlag()) {
            shipperString.append(FAX).append(Optional.ofNullable(shipper.getFax())
                    .map(String::toUpperCase)
                    .orElse(""));
        }
        response.setShipper(String.valueOf(shipperString));
    }
    private void setConsignee(BlnoMaintenanceInfoRespVO response,BlnoScnDO consignee){
        StringBuilder consigneeString = new StringBuilder();
        if (consignee != null) {
            consigneeString.append(Optional.ofNullable(consignee.getCompanyName())
                    .map(String::toUpperCase)
                    .orElse("")).append(System.lineSeparator());
        }
        // 公司地址（根据打印标识）
        if (consignee != null && consignee.getAddressPrintFlag() !=null && consignee.getAddressPrintFlag()) {
            consigneeString.append(Optional.ofNullable(consignee.getCompanyAddress())
                    .map(String::toUpperCase)
                    .orElse("")).append(System.lineSeparator());
        }
        // 电话（根据打印标识）
        if (consignee != null && consignee.getTelephonePrintFlag()!=null &&consignee.getTelephonePrintFlag()) {
            consigneeString.append(TEL).append(Optional.ofNullable(consignee.getTelephone())
                    .map(String::toUpperCase)
                    .orElse("")).append(System.lineSeparator());
        }
        // 邮箱（根据打印标识）
        if (consignee != null && consignee.getEmailPrintFlag() != null && consignee.getEmailPrintFlag()) {
            consigneeString.append(EMAIL).append(Optional.ofNullable(consignee.getEmail())
                    .map(String::toUpperCase)
                    .orElse("")).append(System.lineSeparator());
        }
        // 传真（根据打印标识）
        if (consignee != null && consignee.getFaxPrintFlag() !=null && consignee.getFaxPrintFlag()) {
            consigneeString.append(FAX).append(Optional.ofNullable(consignee.getFax())
                    .map(String::toUpperCase)
                    .orElse(""));
        }
        response.setConsignee(String.valueOf(consigneeString));
    }
    private void setNotify(BlnoMaintenanceInfoRespVO response,BlnoScnDO notify){
        StringBuilder notifyString = new StringBuilder();
        if (notify != null) {
            notifyString.append(Optional.ofNullable(notify.getCompanyName())
                    .map(String::toUpperCase)
                    .orElse("")).append(System.lineSeparator());
        }
        // 公司地址（根据打印标识） 地址都换行 所有字段大写：公司\地址\ 电话 传真\邮箱
        if (notify != null && notify.getAddressPrintFlag() != null && notify.getAddressPrintFlag()) {
            notifyString.append(Optional.ofNullable(notify.getCompanyAddress())
                    .map(String::toUpperCase)
                    .orElse("")).append(System.lineSeparator());
        }
        // 电话（根据打印标识）
        if (notify != null && notify.getTelephonePrintFlag() != null && notify.getTelephonePrintFlag()) {
            notifyString.append(Optional.ofNullable(notify.getTelephone())
                    .map(String::toUpperCase)
                    .orElse("")).append(System.lineSeparator());
        }
        // 邮箱（根据打印标识）
        if (notify != null && notify.getEmailPrintFlag() != null && notify.getEmailPrintFlag()) {
            notifyString.append(Optional.ofNullable(notify.getEmail())
                    .map(String::toUpperCase)
                    .orElse("")).append(System.lineSeparator());
        }
        // 传真（根据打印标识）
        if (notify != null && notify.getFaxPrintFlag() != null && notify.getFaxPrintFlag()) {
            notifyString.append(Optional.ofNullable(notify.getFax())
                    .map(String::toUpperCase)
                    .orElse(""));
        }
        response.setNotifyParty(String.valueOf(notifyString));
    }
    private void setVesselVoyageNo(BlnoMaintenanceInfoRespVO response, BlnoVslvoyDO vslvoy){
        if (vslvoy != null) {
            String vesselCode = Optional.ofNullable(vslvoy.getVesselCode())
                    .map(String::toUpperCase)
                    .orElse("");
            VesselNameListByImoCodesReqDTO vesselNameReqDTO = new VesselNameListByImoCodesReqDTO();
            vesselNameReqDTO.setCharacter(vesselCode);
            vesselNameReqDTO.setType("OTHER");
            vesselNameReqDTO.setEnabled(true);
            vesselNameReqDTO.setVessel(true);
            try {
                CommonResult<List<VesselNamesRespDTO>> rpcCharacterResp = vesselDataApi.listByType(vesselNameReqDTO);
                String vesselNameEn = Optional.ofNullable(rpcCharacterResp.getData())
                        .filter(list -> !list.isEmpty())
                        .map(list -> list.get(0).getVesselNameEn())
                        .orElse("");
                response.setVesselVoyageNo(vesselNameEn.toUpperCase() + "  " +
                        Optional.ofNullable(vslvoy.getVoyageEx())
                                .map(String::toUpperCase)
                                .orElse(""));
            } catch (Exception e) {
                // 处理RPC调用异常
                log.error(e.getMessage(),e);
                throw e;
            }
        }

    }
    private void setPortOfLoading(BlnoMaintenanceInfoRespVO response, @NotNull BlnoDO blnoDO){
        if (!ObjectUtils.isEmpty(blnoDO.getIssueFeederBl()) && blnoDO.getIssueFeederBl() && !CNSHA.equalsIgnoreCase(blnoDO.getPolCode())){
            response.setPortOfLoading(Optional.ofNullable(blnoDO.getPorDesc())
                    .map(String::toUpperCase)
                    .orElse(""));
        }else {
            response.setPortOfLoading(Optional.ofNullable(blnoDO.getPolDesc())
                    .map(String::toUpperCase)
                    .orElse(""));
        }
    }

    private void setPortOfDischarge(BlnoMaintenanceInfoRespVO response, @NotNull BlnoDO blnoDO){
        if (!ObjectUtils.isEmpty(blnoDO.getIssueFeederBl()) && blnoDO.getIssueFeederBl() && !CNSHA.equalsIgnoreCase(blnoDO.getPolCode())){
            response.setPortOfDischarge(Optional.ofNullable(blnoDO.getPolDesc())
                    .map(String::toUpperCase)
                    .orElse(""));
        }else {
            response.setPortOfDischarge(Optional.ofNullable(blnoDO.getPodDesc())
                    .map(String::toUpperCase)
                    .orElse(""));
        }
    }

    private void setMarksAndContainers(BlnoMaintenanceInfoRespVO response, @NotNull BlnoCargoDO blnoCargoDO, List<BlnoCntrDO> cntrList, BlnoDO blnoDO){

        StringBuilder marks = new StringBuilder();
        StringBuilder containers = new StringBuilder();
        StringBuilder marksAndContainers = new StringBuilder();
        //取marks
        if (blnoCargoDO.getMarks() != null) {
            String marksUpperCase = blnoCargoDO.getMarks().toUpperCase();
            marks.append(marksUpperCase);
            marksAndContainers.append(marksUpperCase);
            response.setMarks(String.valueOf(marks));
        }
        //组装箱信息列表
        if (!CollectionUtils.isEmpty(cntrList)) {
            StringBuilder containersTemp = new StringBuilder();
            for (BlnoCntrDO cntrDO : cntrList) {
                // 箱号（最长12位，不足12位时末尾补空格）+空格+斜杠+铅封号+空格+箱尺寸+箱类型+箱状态
                containersTemp.append(padRight(Optional.ofNullable(cntrDO.getCntrNo())
                                .map(String::toUpperCase)
                                .orElse(""), 13)).append("/")
                        //铅封号存在多个拼接在一起的情况，若整行超出时需要换行。
                        .append(Optional.ofNullable(cntrDO.getSealNo())
                                .map(String::toUpperCase)
                                .orElse(""))
                        .append(" ")
                        .append(Optional.ofNullable(cntrDO.getCntrSize())
                                .map(String::toUpperCase)
                                .orElse(""))
                        .append(Optional.ofNullable(cntrDO.getCntrType())
                                .map(String::toUpperCase)
                                .orElse(""))
                        .append(Optional.ofNullable(blnoDO.getCntrStatus())
                                .map(String::toUpperCase)
                                .orElse(""))
                        .append(System.lineSeparator());
            }
            if (containersTemp.toString().endsWith(System.lineSeparator())){
                String substring = containersTemp.substring(0, containersTemp.length() - 2);
                containers.append(substring);
            }else {
                containers.append(containersTemp);
            }
            response.setContainers(String.valueOf(containers));
        }
        if (containers.length() > 1) {
            marksAndContainers.append(System.lineSeparator()).append(System.lineSeparator()).append(containers);
        }
        response.setMarksAndContainers(String.valueOf(marksAndContainers));
    }
    private void setTotalPackagesInWords(BlnoMaintenanceInfoRespVO response, @NotNull List<BlnoCntrDO> cntrList, BlnoDO blnoDO){
        StringBuilder total = new StringBuilder();
        Map<String, Long> cntrTypeCount = cntrList.stream().collect(Collectors.groupingBy(cntr -> cntr.getCntrSize() + cntr.getCntrType(), Collectors.counting()));
        // 构建统计字符串 (格式: 5x20GP 5x40HC)
        StringBuilder containerStats = new StringBuilder();
        cntrTypeCount.forEach((type, count) -> {
            if (containerStats.length() > 0) {
                containerStats.append(" ");
            }
            containerStats.append(count).append("x").append(type);
        });

        if (cntrList.size() > 1) {
            total.append("TOTAL:").append(containerStats).append(" CONTAINERS ONLY.");
        } else {
            total.append("TOTAL:").append(containerStats).append(" CONTAINER ONLY.");
        }
        LambdaQueryWrapper<BaseCodeDO> queryWrapperBase = new LambdaQueryWrapperX<BaseCodeDO>()
                .eq(BaseCodeDO::getCode, blnoDO.getShippingTerm())
                .eq(BaseCodeDO::getCodeCategory, BaseCodeCategoryEnum.SHIPPING_TERM.getValue())
                .eq(BaseCodeDO::getEnable,true)
                .orderByAsc(BaseCodeDO::getSortNo);
        List<BaseCodeDO> baseCodeDos = baseCodeMapper.selectList(queryWrapperBase);
        String baseCode = "";
        if (!baseCodeDos.isEmpty()) {
            BaseCodeDO baseCodeDO = baseCodeDos.get(0);
            baseCode = baseCodeDO.getDescEn();
        }
        response.setTotalPackagesInWords(baseCode.toUpperCase() + System.lineSeparator() + total);
    }
    private void setFreightCharges(BlnoMaintenanceInfoRespVO response, @NotNull List<BlnoPaymentDO> paymentDO, List<DictDataRespDTO> dictDataList){
        List<BlnoPaymentDO> blnoPaymentList = paymentDO.stream()
                .filter(x-> x.getPrintFlag() && !SiPaymentConstants.CHARGE_CODE_FRT.equals(x.getChargeCode()))
                .collect(Collectors.toList());

        StringBuilder freightCharges = new StringBuilder();
        for (BlnoPaymentDO payment : blnoPaymentList) {
            String paymentType = StringUtils.isEmpty(payment.getDesignatedPayment())?payment.getDefaultPayment():payment.getDesignatedPayment();
            if (StringUtils.isEmpty(paymentType)) {
                continue;
            }
            DictDataRespDTO matchedDict = dictDataList.stream()
                    .filter(dict -> paymentType.equals(dict.getValue()))
                    .findFirst()
                    .orElse(null);

            if (matchedDict != null) {
                String chargeCode = Optional.ofNullable(payment.getChargeCode())
                        .map(String::toUpperCase)
                        .orElse("");
                String labelEn = Optional.ofNullable(matchedDict.getLabelEn())
                        .map(String::toUpperCase)
                        .orElse("");

                if (ObjectUtils.isEmpty(freightCharges)){
                    freightCharges.append(chargeCode).append(" ").append(labelEn);
                }else {
                    freightCharges.append(System.lineSeparator()).append(chargeCode).append(" ").append(labelEn);
                }
            }
        }
        response.setFreightCharges(freightAndChargesContentGroup(freightCharges.toString()));

    }
    private void setPrepaidCollect(BlnoMaintenanceInfoRespVO response, @NotNull List<BlnoPaymentDO> paymentDO, List<DictDataRespDTO> dictDataList){
        BlnoPaymentDO ftr = paymentDO.stream().filter(x -> SiPaymentConstants.CHARGE_CODE_FRT.equals(x.getChargeCode())).limit(1).findFirst().orElse(null);
        String prepaidCollect = "";
        if (ftr != null) {
            //指定付款方式为空时，取默认付款方式。frt还运费默认勾选必须打印，无需过滤打印条件
            String chargeCode = StringUtils.isEmpty(ftr.getDesignatedPayment())?ftr.getDefaultPayment():ftr.getDesignatedPayment();
            Optional<DictDataRespDTO> matchedDict = dictDataList.stream()
                    .filter(dict -> chargeCode.equals(dict.getValue()))
                    .findFirst();
            if (matchedDict.isPresent()) {
                prepaidCollect = "FREIGHT " + matchedDict.get().getLabelEn();
            }
        }
        response.setPrepaidCollect(prepaidCollect);
    }
    private void setPrepaidAt(BlnoMaintenanceInfoRespVO response, @NotNull BlnoDO blnoDO){
        PortListByCodeReqDTO paidLand = new PortListByCodeReqDTO();
        boolean issueFeederBl = !ObjectUtils.isEmpty(blnoDO.getIssueFeederBl()) && blnoDO.getIssueFeederBl();
        boolean issueFeederPlace = !ObjectUtils.isEmpty(blnoDO.getIssueFeederPlace()) && blnoDO.getIssueFeederPlace();
        if (issueFeederBl || issueFeederPlace ) {
            paidLand.setPortCodeList(Collections.singletonList(blnoDO.getPorCode()));
        } else {
            paidLand.setPortCodeList(Collections.singletonList(blnoDO.getPolCode()));
        }
        String portNameEn = "";
        CommonResult<List<PortListByCodeRespDTO>> paidLandResult = portApi.listByCodeList(paidLand);
        List<PortListByCodeRespDTO> paidLandList = paidLandResult.getCheckedData();
        if (!paidLandList.isEmpty()) {
            PortListByCodeRespDTO portListByCodeRespDTO = paidLandList.get(0);
            if (StringUtils.isNotBlank(portListByCodeRespDTO.getPortNameEn())) {
                portNameEn = portListByCodeRespDTO.getPortNameEn();
            }
        }
        response.setPrepaidAt(portNameEn);
    }
    private void setLadenOnBoard(BlnoMaintenanceInfoRespVO response, @NotNull BlnoDO blnoDO,BlnoVslvoyDO vslvoy){
        response.setLadenOnBoard(this.getLadenOnBoard(blnoDO,vslvoy));
    }
    private String getLadenOnBoard(@NotNull BlnoDO blnoDO,BlnoVslvoyDO vslvoy){
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM d yyyy", Locale.ENGLISH);
        if (ObjectUtils.isEmpty(blnoDO.getIssueDate())){
            try {
                VesselVoyReqDTO vesselVoyReqDTO = new VesselVoyReqDTO();
                vesselVoyReqDTO.setLaneCode(vslvoy.getLaneCodeEx());
                vesselVoyReqDTO.setPortCode(vslvoy.getPolCode());
                vesselVoyReqDTO.setVoy(vslvoy.getVoyageEx());
                vesselVoyReqDTO.setVesselCodeList(Arrays.asList(vslvoy.getVesselCode()));
                vesselVoyReqDTO.setSystemNumber(SystemEnums.DOC.getSystemNumber());
                List<VesselVoyRespDTO> listVesselVoy = voyScheduleService.listVesselVoy(vesselVoyReqDTO);
                if (!ObjectUtils.isEmpty(listVesselVoy) && !ObjectUtils.isEmpty(listVesselVoy.get(0))) {
                    String atu = listVesselVoy.get(0).getAtu();
                    if (!ObjectUtils.isEmpty(atu)){
                        DateTimeFormatter atuFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
                        LocalDateTime atuLocalDateTime = LocalDateTime.parse(atu, atuFormatter);
                        return atuLocalDateTime.format(formatter);
                    }else {
                        return null;
                    }
                }else {
                    return null;
                }
            } catch (Exception e) {
                log.error("caught an exception on get businessDate", e);
                throw ServiceExceptionUtil.exception(BlnoFreightErrorCodeConstants.BLNO_FREIGHT_ETU_IS_NULL, e.toString());
            }
        }else {
            return blnoDO.getIssueDate().format(formatter);
        }
    }


    private void setOriginalBlNumber(BlnoMaintenanceInfoRespVO response, @NotNull Integer printBlNumber){
        String numberInWords;
        switch (printBlNumber) {
            case 1:
                numberInWords = "ONE";
                response.setOriginalBlNumber(numberInWords + "(" + printBlNumber + ")");
                break;
            case 2:
                numberInWords = "TWO";
                response.setOriginalBlNumber(numberInWords + "(" + printBlNumber + ")");
                break;
            case 3:
                numberInWords = "THREE";
                response.setOriginalBlNumber(numberInWords + "(" + printBlNumber + ")");
                break;
            default:
                response.setOriginalBlNumber("ONE(1)");
        }
    }
    private void setPlaceDateOfIssue(BlnoMaintenanceInfoRespVO response, @NotNull BlnoDO blnoDO){
        LambdaQueryWrapperX<PortDescDO> queryWrapper = new LambdaQueryWrapperX<>();
        queryWrapper.eq(PortDescDO::getPortCode, blnoDO.getIssuePlace());
        queryWrapper.eq(PortDescDO::getEnable, true);
        List<PortDescDO> portDescList = portDescMapper.selectList(queryWrapper);
        StringBuilder placeDateOfIssue = new StringBuilder();
        if (!ObjectUtils.isEmpty(portDescList)){
            PortDescDO portDescDo = Optional.of(portDescList.stream().filter(PortDescDO::getDefaultFlag).findFirst()).orElse(null).get();
            if (ObjectUtils.isEmpty(portDescDo)){
                placeDateOfIssue.append(portDescList.get(0).getPortDesc().toUpperCase());
            }else {
                placeDateOfIssue.append(portDescDo.getPortDesc().toUpperCase());
            }
            placeDateOfIssue.append(System.lineSeparator()).append(response.getLadenOnBoard());
            response.setPlaceDateOfIssue(placeDateOfIssue.toString());
        }
    }

    private void setTranshipmentContact(BlnoMaintenanceInfoRespVO response,@NotNull BlnoDO blnoDO,  @NotNull List<BlnoVslvoyDO> vslvoyList){
        BlnoVslvoyDO blnoVslvoyDoStream = vslvoyList.stream()
                .filter(x -> x.getPolCode().equals(blnoDO.getPolCode()))
                .findFirst()
                .orElse(new BlnoVslvoyDO());

        // 1. 优先按港口+航线代码查询
        LambdaQueryWrapperX<BlnoContactDO> queryWrapper2 = new LambdaQueryWrapperX<>();
        queryWrapper2.eq(BlnoContactDO::getPortCode, blnoDO.getDelCode());
        queryWrapper2.like(BlnoContactDO::getLaneCode, blnoVslvoyDoStream.getLaneCodeEx());
        List<BlnoContactDO> blnoContactList = blnoContactMapper.selectList(queryWrapper2);

        // 2. 若未找到，按港口+航线代码为空查询
        if (blnoContactList.isEmpty()) {
            LambdaQueryWrapperX<BlnoContactDO> fallbackWrapper = new LambdaQueryWrapperX<>();
            fallbackWrapper.eq(BlnoContactDO::getPortCode, blnoDO.getDelCode())
                    .and(wrapper -> wrapper.isNull(BlnoContactDO::getLaneCode).or().eq(BlnoContactDO::getLaneCode, ""));
            blnoContactList = blnoContactMapper.selectList(fallbackWrapper);
        }
        if (!blnoContactList.isEmpty()) {
            response.setTranshipmentContact(Optional.ofNullable(blnoContactList.get(0).getAgentNm()).map(String::toUpperCase).orElse("") + " " + Optional.ofNullable(blnoContactList.get(0).getAddress()).map(String::toUpperCase).orElse(""));
        }
    }
    private void setAttachment(BlnoMaintenanceInfoRespVO response ,String printType){
        //唛头+箱清单列表处附件判断 31*26上限
        if (calculateRowsNumber(response.getMarksAndContainers(),MARKS_CONTAINERS_MAX_COLUMN)> MARKS_CONTAINERS_MAX_LINE){
            //判断单独打印唛头时是否超出行数上线，marks+空行+CONTAINERS_SEE_ATTACHMENT话术
            String newMarksAndContainers = response.getMarks() + System.lineSeparator() + System.lineSeparator() + CONTAINERS_SEE_ATTACHMENT;
            if (calculateRowsNumber(newMarksAndContainers,MARKS_CONTAINERS_MAX_COLUMN) > MARKS_CONTAINERS_MAX_LINE){
                //单独打印唛头也超出时，重新组装MarksAndContainers。marks和Containers都打印到附件页,原位置打印固定话术
                this.setMarksAtt(response,response.getMarks());
                this.setContainerAtt(response,response.getContainers());
                response.setMarksAndContainers(MARK_SEE_ATTACHME+System.lineSeparator()+CONTAINERS_SEE_ATTACHMENT);
            }else {
                response.setMarksAndContainers(newMarksAndContainers);
                this.setContainerAtt(response,response.getContainers());
            }
        }
        //判断收发通是否需要打印到货描处，且copy和draft件货描限制不一致copy: 34*17 draft: 34*23  收发通：57*7
        String exceedShipper = null;
        if (calculateRowsNumber(response.getShipper(),SCN_MAX_COLUMN) >SCN_MAX_LINE){
            //重新拼接shipper
            List<String> shipperSplit = splitScnContent(response.getShipper(), SHIPPER_CONNECTOR);
            response.setShipper(shipperSplit.get(0));
            exceedShipper = shipperSplit.get(1);
        }

        String exceedConsignee = null;
        if (calculateRowsNumber(response.getConsignee(),SCN_MAX_COLUMN) >SCN_MAX_LINE){
            //重新拼接Consignee
            List<String> consigneeSplit = splitScnContent(response.getConsignee(), CONSIGNEE_CONNECTOR);
            response.setConsignee(consigneeSplit.get(0));
            exceedConsignee = consigneeSplit.get(1);
        }

        String exceedNotify = null;
        if (calculateRowsNumber(response.getNotifyParty(),SCN_MAX_COLUMN) >SCN_MAX_LINE){
            //重新拼接Notify
            List<String> notifySplit = splitScnContent(response.getNotifyParty(), NOTIFY_CONNECTOR);
            response.setNotifyParty(notifySplit.get(0));
            exceedNotify = notifySplit.get(1);
        }
        StringBuilder exceedScn = new StringBuilder();
        if (!ObjectUtils.isEmpty(exceedShipper)){
            if(ObjectUtils.isEmpty(exceedScn)){
                exceedScn.append(exceedShipper);
            }else {
                exceedScn.append(System.lineSeparator()).append(exceedShipper);
            }
        }
        if (!ObjectUtils.isEmpty(exceedConsignee)){
            if(ObjectUtils.isEmpty(exceedScn)){
                exceedScn.append(exceedConsignee);
            }else {
                exceedScn.append(System.lineSeparator()).append(exceedConsignee);
            }
        }
        if (!ObjectUtils.isEmpty(exceedNotify)){
            if(ObjectUtils.isEmpty(exceedScn)){
                exceedScn.append(exceedNotify);
            }else {
                exceedScn.append(System.lineSeparator()).append(exceedNotify);
            }
        }
        StringBuilder goodsDescAndScn = new StringBuilder();
        goodsDescAndScn.append(response.getDescriptionOfGoods()).append(System.lineSeparator()).append(System.lineSeparator()).append(exceedScn);

        if (ObjectUtils.isEmpty(printType) || PRINT_TYPE_COPYE.equalsIgnoreCase(printType)){
            //打印电放 copy: 34*17
            if (calculateRowsNumber(goodsDescAndScn.toString(),GOODS_DESC_SCN_MAX_COLUMN) > GOODS_DESC_SCN_MAX_LINE_17){
                //货描+超出的收发通，超出了货描板块上限，则超出的收发通部分打印在货描处，货描内容打印到附件页
                this.setGoodsDescAtt(response,response.getDescriptionOfGoods());
                response.setDescriptionOfGoods(GOODS_DES_SEE_ATTACHMENT+System.lineSeparator()+System.lineSeparator()+exceedScn);
            }else {
                response.setDescriptionOfGoods(goodsDescAndScn.toString());
            }
        }else {
            //非电放 draft: 34*23
            if (calculateRowsNumber(goodsDescAndScn.toString(),GOODS_DESC_SCN_MAX_COLUMN) > GOODS_DESC_SCN_MAX_LINE_23){
                //货描+超出的收发通，超出了货描板块上限，则超出的收发通部分打印在货描处，货描内容打印到附件页
                this.setGoodsDescAtt(response,response.getDescriptionOfGoods());
                response.setDescriptionOfGoods(GOODS_DES_SEE_ATTACHMENT+System.lineSeparator()+System.lineSeparator()+exceedScn);
            }else {
                response.setDescriptionOfGoods(goodsDescAndScn.toString());
            }
        }
    }
    private int calculateRowsNumber(String content ,int lineSize){
        if (!ObjectUtils.isEmpty(content)){
            int rowsNumber = 0;
            String[] split = content.split(System.lineSeparator());
            rowsNumber = rowsNumber+split.length;
            for (String text : split) {
                if (text.length()>lineSize){
                    int i = text.length() / lineSize;
                    rowsNumber = rowsNumber+i;
                }
            }
            return rowsNumber;
        }else {
            return 0;
        }
    }
    private List<String> splitScnContent(@NotNull String scnContent, String connector){
        //收发通：57*7
        ArrayList<String> strings = new ArrayList<>();
        StringBuilder firstString = new StringBuilder();
        StringBuilder secondString = new StringBuilder();


        int currentLow = 0;

        for (String s : scnContent.split(System.lineSeparator())) {
            if(s.length()<=57){
                currentLow++;
                if (currentLow <= 7){
                    if (ObjectUtils.isEmpty(firstString)){
                        firstString.append(s);
                    }else {
                        firstString.append(System.lineSeparator()).append(s);
                    }
                } else if (currentLow >7) {
                    if (ObjectUtils.isEmpty(secondString)){
                        secondString.append(s);
                    }else {
                        secondString.append(System.lineSeparator()).append(s);
                    }
                }
            }else {
                List<String> result = new ArrayList<>();
                int length = s.length();

                for (int i = 0; i < length; i += 57) {
                    int end = Math.min(length, i + 57);
                    result.add(s.substring(i, end));
                }
                StringBuilder longStringFirst = new StringBuilder();
                StringBuilder longStringsecond = new StringBuilder();
                for (String r : result) {
                    currentLow++;
                    if (currentLow <= 7){
                        longStringFirst.append(r);
                    } else if (currentLow > 7) {
                        longStringsecond.append(r);
                    }
                }

                if (ObjectUtils.isEmpty(firstString)){
                    firstString.append(longStringFirst);
                }else {
                    firstString.append(System.lineSeparator()).append(longStringFirst);
                }

                if (ObjectUtils.isEmpty(secondString)){
                    secondString.append(longStringsecond);
                }else {
                    secondString.append(System.lineSeparator()).append(longStringsecond);
                }
            }
        }
        if (!ObjectUtils.isEmpty(secondString)){
            String var1 = firstString.substring(firstString.length() - connector.length(), firstString.length());
            String var2 = firstString.substring(0, firstString.length() - connector.length());
            strings.add(var2+connector);
            strings.add(connector+var1+secondString);
        }else {
            strings.add(firstString.toString());
            strings.add(secondString.toString());
        }
        return strings;
    }



    private void setMarksAtt(BlnoMaintenanceInfoRespVO response,String marksAtt){
        if (ObjectUtils.isEmpty(response.getAttachment())){
            response.setAttachment(new BlnoPrintAttachmentRespVO());
            response.getAttachment().setBlNo(response.getBlNo());
        }
        response.getAttachment().setMarksAtt(marksAtt);
    }

    private void setContainerAtt(BlnoMaintenanceInfoRespVO response,String containerAtt){
        if (ObjectUtils.isEmpty(response.getAttachment())){
            response.setAttachment(new BlnoPrintAttachmentRespVO());
            response.getAttachment().setBlNo(response.getBlNo());
        }
        response.getAttachment().setContainerAtt(containerAtt);
    }
    private void setGoodsDescAtt(BlnoMaintenanceInfoRespVO response,String goodsDescAtt){
        if (ObjectUtils.isEmpty(response.getAttachment())){
            response.setAttachment(new BlnoPrintAttachmentRespVO());
            response.getAttachment().setBlNo(response.getBlNo());
        }
        response.getAttachment().setGoodsDescAtt(goodsDescAtt);
    }
    /**
     * 判断是否为拼箱集装箱并获取拼箱提单号列表
     *
     * @param vesselCode  船名代码
     * @param voyage      航次
     * @param currentBlNo 当前提单号
     * @param cntrNo      箱号
     * @return 拼箱提单号列表，如果为空则表示不是拼箱
     */
    private List<String> getLclBlNos(String vesselCode, String voyage, String currentBlNo, String cntrNo) {
        List<String> lclBlNos = new ArrayList<>();
        // 1. 根据船名和航次查询所有相关的提单主信息
        LambdaQueryWrapper<BlnoDO> blnoQueryWrapper = new LambdaQueryWrapper<>();
        blnoQueryWrapper.eq(BlnoDO::getBkgVesselCode, vesselCode);
        blnoQueryWrapper.eq(BlnoDO::getBkgVoyage, voyage).eq(BlnoDO::getValid, SiValidEnum.VALID.getValue()).eq(BlnoDO::getPodBlFlag,true);
        List<BlnoDO> blnoList = blnoMapper.selectList(blnoQueryWrapper);

        // 2. 排除当前提单号的记录
        Map<Long, String> blnoIdToBlNoMap = blnoList.stream()
                .filter(blno -> !blno.getBlNo().equals(currentBlNo))
                .collect(Collectors.toMap(BlnoDO::getId, BlnoDO::getBlNo));

        // 3. 查询这些提单主信息关联的箱号信息
        if (!blnoIdToBlNoMap.isEmpty()) {
            LambdaQueryWrapper<BlnoCntrDO> cntrQueryWrapper = new LambdaQueryWrapper<>();
            cntrQueryWrapper.in(BlnoCntrDO::getBlnoId, blnoIdToBlNoMap.keySet());
            cntrQueryWrapper.eq(BlnoCntrDO::getCntrNo, cntrNo);
            cntrQueryWrapper.eq(BlnoCntrDO::getDeleted, false);
            List<BlnoCntrDO> cntrList = blnoCntrMapper.selectList(cntrQueryWrapper);

            // 4. 收集使用了该箱号的其他提单号
            if (!cntrList.isEmpty()) {
                for (BlnoCntrDO cntr : cntrList) {
                    String blNo = blnoIdToBlNoMap.get(cntr.getBlnoId());
                    if (blNo != null && !lclBlNos.contains(blNo)) {
                        lclBlNos.add(blNo);
                    }
                }
            }
        }

        return lclBlNos;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void printTheDraft(HttpServletResponse response, Long blnoId) {
        BlnoBlPrintLogCreateReqVO createReqVO = new BlnoBlPrintLogCreateReqVO();
        createReqVO.setBlnoId(blnoId);
        createReqVO.setType(PrintStatusEnum.DRAFTBL.getValue());
        createReqVO.setIsBatch(false);

        byte[] result = this.pdfCreateAnEdit(createReqVO);
        if (createReqVO.getFileName().endsWith(ZIP) ){
            response.setContentType(ResponseContentTypeEnum.ZIP.getContentType());
        }else {
            response.setContentType(ResponseContentTypeEnum.PDF.getContentType());
        }

        response.setHeader("Content-Disposition", "attachment; filename=\"" + createReqVO.getFileName() + "\"");
        response.setContentLength(result.length);

        // 写入响应流
        try (ServletOutputStream outputStream = response.getOutputStream()) {
            outputStream.write(result);
            outputStream.flush();
        } catch (IOException e) {
            log.error(e.getMessage(),e);
            throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.FILE_GENERATE_FAILED);
        }
    }

    /**
     * 根据打印记录和请求参数获取文件二进制数据
     */
     private byte[] getFileBinaryDataByFileId(String fileId) {
        CommonResult<byte[]> fileBinary = null;
        if (fileId != null) {
            fileBinary = fileApi.getFileBinary(Long.valueOf(fileId));
        }
        if (fileBinary == null || fileBinary.getData() == null) {
            throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.FILE_GENERATE_FAILED);
        }
        return fileBinary.getData();
    }

    /**
     * @param input 待检查的字符串
     * @return 存在符合条件的'x'返回true，否则返回false
     */
    private boolean hasWithLeftNumberGreaterThanOne(String input) {
        if (input == null || input.isEmpty()) {
            return false;
        }
        int length = input.length();
        for (int i = 0; i < length; i++) {
            if (input.charAt(i) == 'x') {
                int numStart = i - 1;
                while (numStart >= 0 && Character.isDigit(input.charAt(numStart))) {
                    numStart--;
                }
                numStart++;
                if (numStart <= i - 1) {
                    String numberStr = input.substring(numStart, i);
                    int number = Integer.parseInt(numberStr);
                    if (number > 1) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * 根据指定长度控制字符串长度，不足时右侧补空格
     *
     * @param content 字符串内容
     * @param targetLength  指定长度
     * @return 处理后的字符串
     */
    public static String padRight(String content, int targetLength) {
        if (content == null) {
            content = "";
        }
        if (content.length() >= targetLength) {
            return content;
        }
        int spacesNeeded = targetLength - content.length();
        StringBuilder spaces = new StringBuilder();
        for (int i = 0; i < spacesNeeded; i++) {
            spaces.append(" ");
        }
        return content + spaces;
    }



    /**
     * 获取逗号分隔字符串中逗号后面的内容
     *
     * @param content 包含逗号的字符串
     * @return 逗号后面的内容，如果不存在逗号则返回空字符串
     */
    public static String getContentAfterComma(String content) {
        if (content == null || content.isEmpty()) {
            return "";
        }

        int commaIndex = content.indexOf(',');
        if (commaIndex != -1 && commaIndex < content.length() - 1) {
            return content.substring(commaIndex + 1);
        }

        return "";
    }
    /**
     * 分割 freightAndCharges 内容
     *
     * @param content 包含换行符的字符串
     * @return 第一至7个元素在第一列，第八至14个元素在第二列
     */
    public static String freightAndChargesContentGroup(String content) {
        if (!ObjectUtils.isEmpty(content)){
            String[] freightCharges = content.split(System.lineSeparator());

            List<StringBuilder> stringBuilders = new ArrayList<>(7);

            for (int i = 0; i < freightCharges.length; i++) {
                int lineNumber = i % 7;
                if (lineNumber >= stringBuilders.size() ){
                    StringBuilder emptyString = new StringBuilder();
                    emptyString.append(padRight(freightCharges[i],15));
                    stringBuilders.add(lineNumber,emptyString);
                } else{
                    stringBuilders.get(lineNumber).append(padRight(freightCharges[i],15));
                }
            }
            String collect = stringBuilders.stream().collect(Collectors.joining(System.lineSeparator()));
            return collect;
        }
        return null;
    }
    /**
     * 分割 freightAndCharges 内容
     *
     * @param content 包含换行符的字符串
     * @return 分割后的字符串数组，第一个元素为前7行内容，第二个元素为剩余内容
     */
    public static String[] splitFreightAndChargesContent(String content) {
        if (content == null || content.isEmpty()) {
            return new String[]{"", ""};
        }

        // 按换行符分割
        String[] lines = content.split(System.lineSeparator());

        // 前7行作为第一部分
        StringBuilder firstPart = new StringBuilder();
        StringBuilder secondPart = new StringBuilder();

        for (int i = 0; i < lines.length; i++) {
            if (i < 7) {
                if (firstPart.length() > 0) {
                    firstPart.append(System.lineSeparator());
                }
                firstPart.append(lines[i]);
            } else {
                if (secondPart.length() > 0) {
                    secondPart.append(System.lineSeparator());
                }
                secondPart.append(lines[i]);
            }
        }

        return new String[]{firstPart.toString(), secondPart.toString()};
    }

    /**
     * 将字符串中的连字符(-)替换为下划线(_)
     *
     * @param input 输入字符串
     * @return 替换后的字符串
     */
    public static String replaceHyphenWithUnderscore(String input) {
        if (input == null) {
            return "";
        }
        return input.replaceAll("-", "_");
    }
}